Статьи

Сборка игр с Python 3 и Pygame: часть 5

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

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

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

Поле game_over класса Game установлено в False в __init__() класса Game. Основной цикл game_over пока переменная game_over будет установлена ​​в True:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
class Game:
    def __init__(self,
                 caption,
                 width,
                 height,
                 back_image_filename,
                 frame_rate):
        …
        self.game_over = False
        …
         
def run(self):
    while not self.game_over:
        self.surface.blit(self.background_image, (0, 0))
 
        self.handle_events()
        self.update()
        self.draw()
 
        pygame.display.update()
        self.clock.tick(self.frame_rate)

Это все происходит в классе Breakout в следующих случаях:

  • Игрок нажал кнопку ВЫЙТИ из меню.
  • Игрок теряет свою последнюю жизнь.
  • Игрок очистил все кирпичи.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def on_quit(button):
    self.game_over = True
    self.is_game_running = False
 
def handle_ball_collisions(self):
    …
    # Hit floor
    if self.ball.top > c.screen_height:
        self.lives -= 1
        if self.lives == 0:
            self.game_over = True
 
        if not self.bricks:
            self.show_message(‘YOU WIN!!!’, centralized=True)
            self.is_game_running = False
            self.game_over = True
            return
 
def update(self):
    …
    if not self.bricks:
        self.show_message(‘YOU WIN!!!’, centralized=True)
        self.is_game_running = False
        self.game_over = True
        return

Обычно, когда игра заканчивается, мы не хотим, чтобы окно игры просто растворилось. Исключением является случай, если вы нажали кнопку «ВЫЙТИ» в меню. Когда игрок теряет свою последнюю жизнь, Breakout отображает традиционное «ИГРА НАД!» сообщение, и когда игрок выигрывает, он отображает «ВЫ ВЫИГРЫВАЕТЕ!»

Функция show_message() используется в обоих случаях. Он отображает текст в верхней части текущего экрана (игра будет приостановлена) и ждет несколько секунд, прежде чем вернуться. На следующей итерации игрового цикла проверка поля game_over определит, что это True, и программа game_over .

Вот show_message() :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
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 есть только звуковые эффекты, но я покажу вам, как играть фоновую музыку в ваших играх.

Вам нужны звуковые файлы (похожие на файлы изображений) для воспроизведения в качестве звуковых эффектов. Эти файлы могут быть в форматах .wav, .mp3 или .ogg. Breakout сохраняет свои звуковые эффекты в папке sound_effects :

1
2
3
4
5
6
~/git/pygame-breakout > tree sound_effects/
sound_effects/
├── brick_hit.wav
├── effect_done.wav
├── level_complete.wav
└── paddle_hit.wav

Давайте посмотрим, как эти звуковые эффекты загружаются и воспроизводятся в нужное время. Во-первых, для воспроизведения звуковых эффектов (или фоновой музыки) вам необходимо инициализировать звуковую систему Pygame. Это происходит в классе Game: pygame.mixer.pre_init(44100, 16, 2, 4096)

Затем в классе Breakout все звуковые эффекты загружаются из конфигурации в объект pygame.mixer.Sound и сохраняются в словаре:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
# In config.py
sounds_effects = dict(
    brick_hit=’sound_effects/brick_hit.wav’,
    effect_done=’sound_effects/effect_done.wav’,
    paddle_hit=’sound_effects/paddle_hit.wav’,
    level_complete=’sound_effects/level_complete.wav’,
)
 
# In breakout.py
class Breakout(Game):
    def __init__(self):
        …
        self.sound_effects = {
            name: pygame.mixer.Sound(sound)
            for name, sound in c.sounds_effects.items()}
        …

Теперь мы можем воспроизводить звуковые эффекты, когда происходит что-то интересное. Например, когда мяч попадает в кирпич:

1
2
3
4
5
6
7
# Hit brick
for brick in self.bricks:
    edge = intersect(brick, self.ball)
    if not edge:
        continue
 
    self.sound_effects[‘brick_hit’].play()

Звуковой эффект воспроизводится асинхронно, что означает, что игра не останавливается во время воспроизведения звука. Несколько звуковых эффектов могут быть воспроизведены одновременно.

Запись ваших звуковых эффектов проста и полезна. В отличие от визуального дизайна активов, он не требует большого таланта. Любой может сказать «Kaboom!» или «Боинг» или крик «Ты мертв. Удачи в следующий раз!»

Я часто прошу своих детей записывать звуковые эффекты, а также голосовые сообщения, которые сопровождают текстовые сообщения, такие как «ВЫ ВЫИГРЫВАЕТЕ!» или «ИГРА НАД!» Ваше воображение является единственным ограничением.

Фоновая музыка должна играть постоянно. Теоретически, вы можете получить очень крутой звуковой эффект, но более распространенный подход — просто воспроизвести фоновую музыку в цикле. Музыкальные файлы могут быть в формате .wav, .mp3 или .midi. Вот как это делается:

1
2
music = pygame.mixer.music.load(‘background_music.mp3’)
pygame.mixer.music.play(-1, 0.0)

Вы можете одновременно воспроизводить только одну фоновую музыку. Но несколько звуковых эффектов могут воспроизводиться поверх фоновой музыки. Вот что такое смешивание.

Давайте представимся. Бить кирпичом по мячу — это круто, но довольно быстро стареет. Как насчет общей системы спецэффектов? Мы разработаем расширяемую систему спецэффектов, которые связаны с определенными кирпичами и активируются, когда мяч попадает в кирпич.

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

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

Следующая инструкция из модуля breakout.py определяет наши специальные эффекты. Каждый эффект имеет имя (например, long_paddle) и значение, которое состоит из цвета, который будет иметь его кирпич, а также двух функций. Функции определяются как лямбда-функции, которые берут экземпляр Game, который включает в себя все, что может потребоваться изменить специальным эффектом в Breakout.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
special_effects = dict(
   long_paddle=(
       colors.ORANGE,
       lambda g: g.paddle.bounds.inflate_ip(
                   c.paddle_width // 2, 0),
       lambda g: g.paddle.bounds.inflate_ip(
                   -c.paddle_width // 2, 0)),
   slow_ball=(
       colors.AQUAMARINE2,
       lambda g: g.change_ball_speed(-1),
       lambda g: g.change_ball_speed(1)),
   tripple_points=(
       colors.DARKSEAGREEN4,
       lambda g: g.set_points_per_brick(3),
       lambda g: g.set_points_per_brick(1)),
   extra_life=(
       colors.GOLD1,
       lambda g: g.add_life(),
       lambda g: None))

Когда кирпичи созданы, у них есть изменение, которому назначают один из специальных эффектов. Вот код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def create_bricks(self):
   w = c.brick_width
   h = c.brick_height
   brick_count = c.screen_width // (w + 1)
   offset_x = (c.screen_width — brick_count * (w + 1)) // 2
 
   bricks = []
   for row in range(c.row_count):
       for col in range(brick_count):
           effect = None
           brick_color = c.brick_color
           index = random.randint(0, 10)
           if index < len(special_effects):
               x = list(special_effects.values())[index]
               brick_color = x[0]
               effect = x[1:]
 
           brick = Brick(offset_x + col * (w + 1),
                         c.offset_y + row * (h + 1),
                         w,
                         h,
                         brick_color,
                         effect)
           bricks.append(brick)
           self.objects.append(brick)
   self.bricks = bricks

Класс Brick имеет поле эффекта, которое обычно отсутствует, но может получить (шанс 30%) один из специальных эффектов, определенных выше. Обратите внимание, что этот код не знает, какие эффекты доступны. Он просто получает эффект и цвет кирпича и назначает их при необходимости.

В этой версии Breakout я запускаю эффекты только при ударе по кирпичу, но вы можете представить другие сценарии, которые могут вызывать события. Предыдущий эффект сбрасывается (если он был), а затем запускается новый эффект. Функция сброса и время начала эффекта сохраняются для последующего использования.

01
02
03
04
05
06
07
08
09
10
if brick.special_effect is not None:
   # Reset previous effect if any
   if self.reset_effect is not None:
       self.reset_effect(self)
 
   # Trigger special effect
   self.effect_start_time = datetime.now()
   brick.special_effect[0](self)
   # Set current reset effect function
   self.reset_effect = brick.special_effect[1]

Если новый эффект не был запущен, нам все равно нужно сбросить текущее событие, когда оно истечет. Это происходит в методе update() . В каждом кадре функция сброса текущего эффекта была назначена полю reset_effect . Если время, reset_effect() с момента запуска текущего эффекта, превысило его длительность, то reset_effect() функция reset_effect() а для поля reset_effect задано значение None (то есть активного эффекта сейчас нет).

1
2
3
4
5
6
# Reset special effect if needed
if self.reset_effect:
    elapsed = datetime.now() — self.effect_start_time
    if elapsed >= timedelta(seconds=c.effect_duration):
        self.reset_effect(self)
        self.reset_effect = None

Эффект длинного весла работает, увеличивая весло на 50%. Функция сброса просто изменяет его до нормального. Цвет кирпича оранжевый:

1
2
3
4
5
6
long_paddle=(
   colors.ORANGE,
   lambda g: g.paddle.bounds.inflate_ip(
               c.paddle_width // 2, 0),
   lambda g: g.paddle.bounds.inflate_ip(
              -c.paddle_width // 2, 0)),

Другим эффектом, который помогает в погоне за мячом, является эффект медленного мяча, который просто замедляет скорость мяча на одну единицу. Цвет кирпича — Аквамарин.

1
2
3
slow_ball=(colors.AQUAMARINE2,
          lambda g: g.change_ball_speed(-1),
          lambda g: g.change_ball_speed(1)),

Если вам нужны большие числа, вам понравится эффект тройных баллов, который дает вам три очка за каждый пораженный кирпич вместо стандартного одного очка. Цвет кирпича темно-зеленый.

1
2
3
tripple_points=(colors.DARKSEAGREEN4,
               lambda g: g.set_points_per_brick(3),
               lambda g: g.set_points_per_brick(1)),

Наконец, очень полезный эффект — эффект дополнительных жизней. Это просто дает вам дополнительную жизнь. Сброс не требуется на самом деле. Цвет кирпича золотой.

1
2
3
extra_life=(colors.GOLD1,
           lambda g: g.add_life(),
           lambda g: None))

Есть несколько естественных направлений для продолжения прорыва. Если вы хотите попробовать свои силы в добавлении новых возможностей и возможностей, вот несколько идей.

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

Добавление второго шара в качестве временного эффекта обязательно создаст много хаоса. Самое сложное в том, чтобы оба шарика были одинаковыми, независимо от того, какой из них был оригиналом. Когда один мяч пропал, игра продолжается с единственным оставленным мячом. Жизнь не потеряна.

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

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

Разработка Breakout с использованием Python 3 и Pygame была очень полезным опытом. Это очень мощная комбинация для 2D-игр (и 3D-игр тоже). Если вам нравится Python и вы хотите создавать свои собственные игры, вы не ошибетесь с Pygame.

Я определенно планирую сделать больше игр с Python и Pygame.

Наконец, помните, у нас есть много контента на Python, доступного для продажи и для изучения на рынке Envato.