image
(Остальные части туториала: первая, вторая, четвёртая, пятая.)

Это третья из пяти частей туториала о создании игр с помощью Python 3 и Pygame. Во второй части мы рассмотрели класс TextObject, используемый для рендеринга текста на экран, создали основное окно и узнали, как отрисовывать объекты: кирпичи, мяч и ракетку.

В этой части мы погрузимся глубже в сердце Breakout и узнаем, как обрабатывать события, познакомимся с основным классом Breakout и увидим, как перемещать различные объекты в игре.

Обработка событий


В Breakout есть три типа событий: события нажатий клавиш, события мыши и события таймера. Основной цикл в классе Game обрабатывает нажатия клавиш и события мыши и передаёт их подписчикам (вызывая функцию-обработчик).

Хотя класс Game очень общий и не обладает знаниями о реализации Breakout, сама подписка и способы обработки событий очень специфичны.

Класс Breakout


В классе Breakout реализуется большинство знаний о том, как управляется игра Breakout. В этой серии туториалов мы несколько раз встретимся с классом Breakout. Вот строки, которые регистрируют различные обработчики событий.

Нужно учесть, что все события клавиш (и для левой, и для правой «стрелки») передаются одному методу-обработчику ракетки.

# Регистрация метода handle_mouse_event() объекта кнопки
self.mouse_handlers.append(b.handle_mouse_event)

# Регистрация метода handle() ракетки для обработки событий клавиш
self.keydown_handlers[pygame.K_LEFT].append(paddle.handle)
self.keydown_handlers[pygame.K_RIGHT].append(paddle.handle)
self.keyup_handlers[pygame.K_LEFT].append(paddle.handle)
self.keyup_handlers[pygame.K_RIGHT].append(paddle.handle)

Обработка нажатий клавиш


Класс Game вызывает зарегистрированные обработчики для каждого события клавиш и передаёт клавишу. Заметьте, что это не класс Paddle. В Breakout единственный объект, который интересуют подобные события — это ракетка. При нажатии или отпускании клавиши вызывается его метод handle(). Объекту Paddle не нужно знать, было ли это событие нажатия или отпускания клавиши, потому что он управляет текущим состоянием с помощью пары булевых переменных: moving_left и moving_right. Если moving_left равна True, то значит, была нажата клавиша «влево», и следующим событием будет отжатие клавиши, которое сбросит переменную. То же самое относится и к клавише «вправо». Логика проста и заключается в переключении состояния этих переменных в ответ на любое событие.

    def handle(self, key):
        if key == pygame.K_LEFT:
            self.moving_left = not self.moving_left
        else:
            self.moving_right = not self.moving_right

Обработка событий мыши


В Breakout есть игровое меню, с которым мы скоро встретимся. Кнопка меню управляет различными событиями мыши, такими как движение и нажатия кнопок (события mouse down и mouse up). В ответ на эти события кнопка обновляет переменную внутреннего состояния. Вот код обработки мыши:

    def handle_mouse_event(self, type, pos):
        if type == pygame.MOUSEMOTION:
            self.handle_mouse_move(pos)
        elif type == pygame.MOUSEBUTTONDOWN:
            self.handle_mouse_down(pos)
        elif type == pygame.MOUSEBUTTONUP:
            self.handle_mouse_up(pos)

    def handle_mouse_move(self, pos):
        if self.bounds.collidepoint(pos):
            if self.state != 'pressed':
                self.state = 'hover'
        else:
            self.state = 'normal'

    def handle_mouse_down(self, pos):
        if self.bounds.collidepoint(pos):
            self.state = 'pressed'

    def handle_mouse_up(self, pos):
        if self.state == 'pressed':
            self.on_click(self)
            self.state = 'hover'

Заметьте, что метод handle_mouse_event(), зарегистрированный для получения событий мыши, проверяет тип события и переадресует его к соответствующему методу, обрабатывающему этот тип события.

Обработка событий таймера


События таймера не обрабатываются в основном цикле. Однако поскольку основной цикл вызывается в каждом кадре, легко проверить, настало ли время определённого события. Вы увидите это позже, когда мы будем обсуждать временные спецэффекты.

Ещё одной ситуацией является необходимость приостановки игры. Например, при отображении сообщения, которое игрок должен прочитать и чтобы при этом ничего его не отвлекало. Метод show_message() класса Breakout использует такой подход и вызывает time.sleep(). Вот соответствующий код:

import config as c

class Breakout(Game):
    def show_message(self, 
                     text, 
                     color=colors.WHITE, 
                     font_name='Arial', 
                     font_size=20, 
                     centralized=False):
        message = TextObject(c.screen_width // 2, 
                             c.screen_height // 2, 
                             lambda: text, color, 
                             font_name, font_size)
        self.draw()
        message.draw(self.surface, centralized)
        pygame.display.update()
        time.sleep(c.message_duration)

Игровой процесс


Игровой процесс (геймплей) — это то место, в котором вступают в дело правила Breakout. Геймплей заключается в перемещении различных объектов в ответ на события и в изменении состояния игры на основании их взаимодействий.

Перемещение ракетки


Вы видели ранее, что класс Paddle реагирует на нажатия клавиш со стрелками, обновляя свои поля moving_left и moving_right. Само движение происходит в методе update(). Здесь выполняются определённые вычисления, если ракетка находится близко к левой или правой границе экрана. Мы не хотим, чтобы ракетка выходила за границы экрана (с учётом заданного смещения).

Поэтому если движение перемещает объект за границы, то код регулирует движение так, чтобы оно останавливалось прямо у границы. Так как ракетка движется только горизонтально, вертикальный компонент перемещения всегда равен нулю.

import pygame

import config as c
from game_object import GameObject


class Paddle(GameObject):
    def __init__(self, x, y, w, h, color, offset):
        GameObject.__init__(self, x, y, w, h)
        self.color = color
        self.offset = offset
        self.moving_left = False
        self.moving_right = False

    ...

    def update(self):
        if self.moving_left:
            dx = -(min(self.offset, self.left))
        elif self.moving_right:
            dx = min(self.offset, c.screen_width - self.right)
        else:
            return

        self.move(dx, 0)

Перемещение мяча


Мяч просто использует функционал базового класса GameObject, который перемещает объекты на основании их скорости (её горизонтального и вертикального компонентов). Как мы вскоре увидим, скорость мяча определяется множеством факторов в классе Breakout. Так как движение заключается просто в прибавлении скорости к текущему положению, то направление, в котором движется мяч, полностью определяется скоростью вдоль горизонтальной и вертикальной осей.

Задание исходной скорости мяча


Мяч в Breakout возникает из ниоткуда в самом начале игры каждый раз, когда игрок теряет жизнь. Он просто материализуется из эфира и начинает падать или ровно вниз, или под небольшим углом. Когда мяч создаётся в методе create_ball(), он получает скорость со случайным горизонтальным компонентом в промежутке от -2 до 2 и вертикальным компонентом, задаваемым в модуле config.py (по умолчанию задано значение 3).

    def create_ball(self):
        speed = (random.randint(-2, 2), c.ball_speed)
        self.ball = Ball(c.screen_width // 2,
                         c.screen_height // 2,
                         c.ball_radius,
                         c.ball_color,
                         speed)
        self.objects.append(self.ball)

Подведём итог


В этой части мы рассмотрели обработку таких событий, как нажатие клавиш, движение мыши и нажатие кнопок мыши. Также мы изучили некоторые элементы геймплея Breakout: перемещение ракетки, движение мяча и управление скоростью мяча.

В четвёртой части мы рассмотрим важную тему распознавания коллизий и увидим, что происходит, когда мяч ударяется об разные игровые объекты: ракетку, кирпичи, стены, потолок и пол. Затем мы уделим внимание игровому меню. Мы создадим собственные кнопки, которые используем в качестве меню и сможем при необходимости показывать и скрывать.

Комментарии (0)