Статьи

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

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

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

Вот функции и возможности, которые мы реализуем:

  • простой универсальный GameObject и TextObject
  • простой универсальный игровой объект
  • простая общая кнопка
  • файл конфигурации
  • обработка событий клавиатуры и мыши
  • кирпичи, весло и мяч
  • управление движением весла
  • обработка столкновения мяча со всем
  • фоновая картинка
  • Звуковые эффекты
  • расширяемая система спецэффектов

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

Пример игрового интерфейса

Полный исходный код доступен здесь .

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

Основной цикл игры запускается и обновляет экран с фиксированными интервалами. Это ваша частота кадров, и она определяет, насколько все гладко. Как правило, игры обновляют экран от 30 до 60 раз в секунду. Если вы идете медленнее, объекты на экране будут казаться рывками.

Внутри основного цикла есть три основных действия: обработка событий, обновление состояния игры и отображение текущего состояния экрана.

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

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

Существует также вспомогательное состояние, которое помогает управлять игрой:

  • Мы показываем меню сейчас?
  • Игра окончена?
  • Игрок выиграл?

Игра должна отображать свое состояние на экране. Это включает в себя рисование геометрических фигур, изображений и текста.

Большинство игр имитируют физическую среду. В Breakout мяч отскакивает от объектов и имеет очень грубую физическую систему твердого тела (если вы можете это так назвать).

Более продвинутые игры могут иметь более сложные и реалистичные физические системы (особенно 3D-игры). Обратите внимание, что в некоторых играх, таких как карточные игры, совсем немного физики, и это совершенно нормально.

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

Например, враги будут преследовать вас и знать ваше местоположение. Прорыв не представляет ИИ. Вы играете против холодных, твердых кирпичей. Тем не менее, ИИ в играх часто очень прост и просто следует простым (или сложным) правилам для достижения псевдоинтеллектуальных результатов.

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

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

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

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

Pygame — это среда Python для программирования игр. Он построен на основе SDL и имеет все хорошее:

  • зрелый
  • большое сообщество
  • открытый источник
  • кросс-платформенный
  • хорошие документы
  • множество примеров игр
  • легко учить

Введите pip install pygame , чтобы установить его. Если вам нужно что-то еще, следуйте инструкциям в разделе « Начало работы » в вики. Если вы запустите macOS Sierra, как я, у вас могут возникнуть проблемы. Я смог без проблем установить Pygame, и казалось, что код работает нормально, но окно игры так и не появилось.

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
├── 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 содержит короткие аудиоклипы, используемые в качестве (как вы уже догадались) звуковых эффектов.

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

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

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

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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 является ядром игры. Он запускает основной цикл. У него много полезного функционала. Давайте рассмотрим метод по методу.

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

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

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
27
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() очень просты. Они просто перебирают все управляемые игровые объекты и вызывают их соответствующие методы. Если два игровых объекта перекрываются, порядок в списке объектов определяет, какой объект будет отображаться первым, а другой будет частично или полностью покрывать его.

1
2
3
4
5
6
7
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, такие как события клавиш и мыши. Для каждого события он вызывает все функции-обработчики, которые зарегистрированы для обработки этого типа события.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
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() чтобы контролировать, когда будет вызываться следующая итерация.

01
02
03
04
05
06
07
08
09
10
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 используемый для визуализации текста на экране. Мы создадим главное окно, включая фоновое изображение, а затем научимся рисовать такие объекты, как шар и весло.

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