Воспроизведение звука

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

Фоновая музыка — это просто музыка, постоянно играющая на фоне. В некоторых играх она не используется, а в некоторых меняется на каждом уровне.

 

Жизни, очки и уровни

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

 

Знакомство с Pygame


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

 


Что такое Pygame?


Pygame — это фреймворк языка Python для программирования игр. Он создан поверх SDL и обладает всем необходимым:

· зрелостью

· хорошим сообществом

· открытым исходным кодом

· кроссплатформенностью

· качественной документацией

· множеством примеров игр

· простотой изучения

 


Установка Pygame


Введите pip install pygame, чтобы установить фреймворк. Если вам нужно что-то ещё, то следуйте инструкциям из раздела Getting Started в Wiki проекта. Если у вас, как и у меня, macOS Sierra, то могут возникнуть проблемы. Мне удалось установить Pygame без сложностей, и код работает отлично, но окно игры никогда не появляется.

Это станет серьёзным препятствием при запуске игры. В конце концов мне пришлось запускать её в Windows внутри VirtualBox VM. Надеюсь, ко времени прочтения этой статьи проблема будет решена.




Архитектура игры


Играм нужно управлять кучей информации и выполнять почти одинаковые операции со множеством объектов. Breakout — это небольшая игра, однако попытка управлять всем в одном файле может оказаться слишком утомительной. Поэтому я решил создать файловую структуру и архитектуру, которая подойдёт и для гораздо более крупных игр.




Структура папок и файлов

 

├── Pipfile

├── Pipfile.lock

├── README.md

├── ball.py

├── breakout.py

├── brick.py

├── button.py

├── colors.py

├── config.py

├── game.py

├── game_object.py

├── images

│ └── background.jpg

├── paddle.py

├── sound_effects

│ ├── brick_hit.wav

│ ├── effect_done.wav

│ ├── level_complete.wav

│ └── paddle_hit.wav

└── text_object.py


Pipfile и Pipfile.lock — это современный способ управления зависимостями в Python. Папка images содержит изображения, используемые игрой (в нашей версии будет только фоновое изображение), а в папке sound_effects directory лежат короткие звуковые клипы, используемые (как можно догадаться) в качестве звуковых эффектов.

Файлы ball.py, paddle.py и brick.py содержат код, относящийся к каждому из этих объектов Breakout. Подробнее я рассмотрю их в следующих частях туториала. Файл text_object.py содержит код отображения текста на экране, а в файле background.py содержится игровая логика Breakout.

Однако существует несколько модулей, создающих произвольный «скелет» общего назначения. Определённые в них классы можно будет использовать в других играх на основе Pygame.

 


Класс GameObject


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

 

from pygame.rect import Rect

 

class GameObject:

def __init__(self, x, y, w, h, speed=(0,0)):

   self.bounds = Rect(x, y, w, h)

   self.speed = speed

 

@property

def left(self):

   return self.bounds.left

 

@property

def right(self):

   return self.bounds.right

 

@property

def top(self):

   return self.bounds.top

 

@property

def bottom(self):

   return self.bounds.bottom

 

@property

def width(self):

   return self.bounds.width

 

@property

def height(self):

   return self.bounds.height

 

@property

def center(self):

   return self.bounds.center

 

@property

def centerx(self):

   return self.bounds.centerx

 

@property

def centery(self):

   return self.bounds.centery

 

def draw(self, surface):

   pass

 

def move(self, dx, dy):

   self.bounds = self.bounds.move(dx, dy)

 

def update(self):

   if self.speed == [0, 0]:

       return

 

   self.move(*self.speed)

GameObject предназначен для того, чтобы быть базовым классом для других объектов. Он непосредственно раскрывает множество свойств его прямоугольника self.bounds, а в своём методе update() он перемещает объект в соответствии с его текущей скоростью. Он ничего не делает в своём методе draw(), который должен быть переопределён подклассами.

 


Класс Game


Класс Game — это ядро игры. Он выполняется в основном цикле. В нём есть множество полезных возможностей. Давайте разберём его метод за методом.

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

Элемент self.objects хранит все игровые объекты, которые должны рендериться и обновляться. Различные обработчики управляют списками функций-обработчиков, которые должны выполняться при определённых событиях.

 

import pygame

import sys

 

from collections import defaultdict

 

 

class Game:

def __init__(self,

            caption,

            width,

            height,

            back_image_filename,

            frame_rate):

   self.background_image = \

       pygame.image.load(back_image_filename)

   self.frame_rate = frame_rate

   self.game_over = False

   self.objects = []

   pygame.mixer.pre_init(44100, 16, 2, 4096)

   pygame.init()

   pygame.font.init()

   self.surface = pygame.display.set_mode((width, height))

   pygame.display.set_caption(caption)

   self.clock = pygame.time.Clock()

   self.keydown_handlers = defaultdict(list)

   self.keyup_handlers = defaultdict(list)

   self.mouse_handlers = []


Методы update() и draw() очень просты. Они обходят все управляемые игровые объекты и вызывают соответствующие им методы. Если два объекта накладываются друг на друга на экране, то порядок списка объектов определяет, какой из них будет рендериться первым, а остальные будут частично или полностью его перекрывать.


def update(self):

   for o in self.objects:

       o.update()

 

def draw(self):

   for o in self.objects:

       o.draw(self.surface)


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

 

def handle_events(self):

   for event in pygame.event.get():

       if event.type == pygame.QUIT:

           pygame.quit()

           sys.exit()

       elif event.type == pygame.KEYDOWN:

           for handler in self.keydown_handlers[event.key]:

               handler(event.key)

       elif event.type == pygame.KEYUP:

             for handler in self.keydown_handlers[event.key]:

               handler(event.key)

       elif event.type in (pygame.MOUSEBUTTONDOWN,

                           pygame.MOUSEBUTTONUP,

                           pygame.MOUSEMOTION):

           for handler in self.mouse_handlers:

               handler(event.type, event.pos)


Наконец, метод run() выполняет основной цикл. Он выполняется до тех пор, пока элемент game_over не принимает значение True. В каждой итерации он рендерит фоновое изображение и вызывает по порядку методы handle_events(), update() и draw().

Затем он обновляет экран, то есть записывает на физический дисплей всё содержимое, которое было отрендерено на текущей итерации. И последнее, но не менее важное — он вызывает метод clock.tick() для управления тем, когда будет вызвана следующая итерация.

 

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)






Заключение

В этой части мы изучили основы программирования игр и все компоненты, участвующие в создании игр. Также мы рассмотрели сам Pygame и узнали, как его установить. Наконец, мы погрузились в архитектуру игры и изучили структуру папок, классы GameObject и Game.

Во второй части мы рассмотрим класс TextObject, используемый для рендеринга текста на экране. Мы создадим основное окно, в том числе и фоновое изображение, а затем узнаем, как отрисовывать объекты (мяч и ракетку).


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: