PyGame: Учебник по программированию игр на Python

Table of contents

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

К концу этой статьи вы сможете:

  • Рисуйте предметы на экране
  • Воспроизводите звуковые эффекты и музыку
  • Обработка пользовательского ввода
  • Реализация циклов обработки событий
  • Опишите, чем игровое программирование отличается от стандартного процедурного программирования на Python

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

Вы можете ознакомиться со всем кодом, приведенным в этой статье, и следовать ему:

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

Предыстория и настройка

pygame представляет собой оболочку на языке Python для библиотеки SDL, которая расшифровывается как Simple DirectMedia Layer. SDL обеспечивает межплатформенный доступ к базовым мультимедийным аппаратным компонентам вашей системы, таким как звук, видео, мышь, клавиатура и джойстик. pygame начал свою работу в качестве замены приостановленного проекта PySDL. Кроссплатформенность как SDL, так и pygame означает, что вы можете писать игры и мультимедийные программы на Python для любой платформы, которая их поддерживает!

Чтобы установить pygame на вашу платформу, используйте соответствующую команду pip:

$ pip install pygame

Вы можете проверить правильность установки, загрузив один из примеров, прилагаемых к библиотеке:

$ python3 -m pygame.examples.aliens

Если появится окно игры, значит, pygame установлен правильно! Если у вас возникнут проблемы, то в Руководстве по началу работы описаны некоторые известные проблемы и предостережения для всех платформ.

Базовая программа PyGame

Прежде чем перейти к конкретике, давайте взглянем на базовую программу pygame. Эта программа создает окно, заливает фон белым цветом и рисует синий круг посередине:

 1# Simple pygame program
 2
 3# Import and initialize the pygame library
 4import pygame
 5pygame.init()
 6
 7# Set up the drawing window
 8screen = pygame.display.set_mode([500, 500])
 9
10# Run until the user asks to quit
11running = True
12while running:
13
14    # Did the user click the window close button?
15    for event in pygame.event.get():
16        if event.type == pygame.QUIT:
17            running = False
18
19    # Fill the background with white
20    screen.fill((255, 255, 255))
21
22    # Draw a solid blue circle in the center
23    pygame.draw.circle(screen, (0, 0, 255), (250, 250), 75)
24
25    # Flip the display
26    pygame.display.flip()
27
28# Done! Time to quit.
29pygame.quit()

Когда вы запустите эту программу, вы увидите окно, которое выглядит следующим образом:

A simple pygame program

Давайте разберем этот код по разделам:

  • Строки 4 и 5 импортируют и инициализируют библиотеку pygame. Без этих строк нет ничего pygame.

  • Строка 8 настраивает окно отображения вашей программы. Вы предоставляете либо список, либо кортеж, который определяет ширину и высоту создаваемого окна. Эта программа использует список для создания квадратного окна по 500 пикселей с каждой стороны.

  • Строки 11 и 12 настраивают игровой цикл для управления завершением программы. Позже в этом руководстве вы познакомитесь с игровыми циклами.

  • Строки с 15 по 17 сканируют и обрабатывают события в игровом цикле. К событиям вы также перейдете чуть позже. В данном случае обрабатывается только одно событие - pygame.QUIT, которое происходит, когда пользователь нажимает кнопку закрытия окна.

  • Строка 20 заполняет окно сплошным цветом. screen.fill() принимает либо список, либо кортеж, указывающий значения RGB для цвета. Поскольку было указано значение (255, 255, 255), окно заполнено белым цветом.

  • Строка 23 рисует круг в окне, используя следующие параметры:

    • screen: окно, на котором нужно нарисовать
    • (0, 0, 255): кортеж, содержащий значения цвета RGB
    • (250, 250): кортеж, задающий координаты центра окружности
    • 75: радиус окружности для рисования в пикселях
  • Строка 26 отображает содержимое дисплея на экране. Без этого вызова в окне ничего не отображается!

  • Строка 29 завершается pygame. Это происходит только после завершения цикла.

Это pygame версия “Hello, World”. Теперь давайте немного углубимся в концепции, лежащие в основе этого кода.

Концепции PyGame

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

Инициализация и модули

Библиотека pygame состоит из ряда конструкций Python, которые включают в себя несколько различных модулей. Эти модули предоставляют абстрактный доступ к определенному оборудованию в вашей системе, а также унифицированные методы работы с этим оборудованием. Например, display обеспечивает единый доступ к вашему видеодисплею, в то время как joystick обеспечивает абстрактное управление джойстиком.

После импорта библиотеки pygame в приведенном выше примере первое, что вы сделали, это инициализировали PyGame с помощью pygame.init(). Эта функция вызывает отдельные init() функции из всех включенных pygame модулей. Поскольку эти модули являются абстракциями для конкретного оборудования, этот шаг инициализации необходим, чтобы вы могли работать с одним и тем же кодом в Linux, Windows и Mac.

Дисплеи и поверхности

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

В pygame все отображается на одном экране, созданном пользователем display, который может быть как окном, так и полноэкранным. Отображение создается с помощью функции .set_mode(),, которая возвращает значение Surface, представляющее видимую часть окна. Именно это Surface вы передаете в такие функции рисования, как pygame.draw.circle(), и содержимое этого Surface выводится на дисплей при вызове pygame.display.flip().

Изображения и прямоугольники

Ваша базовая программа pygame нарисовала фигуру непосредственно на дисплее Surface, но вы также можете работать с изображениями на диске. В image модуль позволяет загрузить и сохранить изображений в различные популярные форматы. Изображения загружаются в Surface объектов, которыми затем можно манипулировать и отображать различными способами.

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

Ладно, хватит теории. Давайте разработаем и напишем игру!

Базовый дизайн игры

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

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

Когда он описывал проекты по разработке программного обеспечения, мой бывший коллега обычно говорил: “Вы не знаете, что делаете, пока не узнаете, чего не делаете”. Имея это в виду, вот некоторые вещи, которые не будут рассмотрены в этом руководстве:

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

Вы можете попробовать свои силы в добавлении этих и других функций в свою собственную программу.

Давайте начнем!

Импорт и инициализация PyGame

После импорта pygame вам также потребуется инициализировать его. Это позволяет pygame подключать его абстракции к вашему конкретному оборудованию:

 1# Import the pygame module
 2import pygame
 3
 4# Import pygame.locals for easier access to key coordinates
 5# Updated to conform to flake8 and black standards
 6from pygame.locals import (
 7    K_UP,
 8    K_DOWN,
 9    K_LEFT,
10    K_RIGHT,
11    K_ESCAPE,
12    KEYDOWN,
13    QUIT,
14)
15
16# Initialize pygame
17pygame.init()

Библиотека pygame определяет множество вещей, помимо модулей и классов. Она также определяет некоторые локальные константы для таких вещей, как нажатия клавиш, движения мыши и атрибуты отображения. Вы ссылаетесь на эти константы, используя синтаксис pygame.<CONSTANT>. Импортируя определенные константы из pygame.locals, вы можете использовать синтаксис <CONSTANT>. Это сэкономит вам несколько нажатий клавиш и улучшит общую читабельность.

Настройка дисплея

Теперь вам нужно на чем-то рисовать! Создайте экран, который будет общим холстом:

 1# Import the pygame module
 2import pygame
 3
 4# Import pygame.locals for easier access to key coordinates
 5# Updated to conform to flake8 and black standards
 6from pygame.locals import (
 7    K_UP,
 8    K_DOWN,
 9    K_LEFT,
10    K_RIGHT,
11    K_ESCAPE,
12    KEYDOWN,
13    QUIT,
14)
15
16# Initialize pygame
17pygame.init()
18
19# Define constants for the screen width and height
20SCREEN_WIDTH = 800
21SCREEN_HEIGHT = 600
22
23# Create the screen object
24# The size is determined by the constant SCREEN_WIDTH and SCREEN_HEIGHT
25screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))

Вы создаете экран для использования, вызывая pygame.display.set_mode() и передавая кортеж или список нужной ширины и высоты. В этом случае размер окна составляет 800x600, как определено константами SCREEN_WIDTH и SCREEN_HEIGHT в строках 20 и 21. Это возвращает значение Surface, которое представляет внутренние размеры окна. Это та часть окна, которой вы можете управлять, в то время как операционная система управляет границами окна и строкой заголовка.

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

Настройка игрового цикла

В каждой игре, от Pong до Fortnite, используется игровой цикл для управления игровым процессом. Игровой цикл выполняет четыре очень важные функции:

  1. Обрабатывает вводимые пользователем данные
  2. Обновляет состояние всех игровых объектов
  3. Обновляется дисплей и аудиовыход
  4. Поддерживается скорость игры

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

  1. Игрок сталкивается с препятствием. (Об обнаружении столкновений вы узнаете позже.)
  2. Игрок закроет окно.

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

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

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

С каждым событием в pygame связано событие типа. В вашей игре основное внимание будет уделяться нажатиям клавиш и закрытию окна. События нажатия клавиши имеют тип события KEYDOWN, а событие закрытия окна имеет тип QUIT. С различными типами событий также могут быть связаны другие данные. Например, тип события KEYDOWN также содержит переменную , которая называется key и указывает, какая клавиша была нажата.

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

27# Variable to keep the main loop running
28running = True
29
30# Main loop
31while running:
32    # Look at every event in the queue
33    for event in pygame.event.get():
34        # Did the user hit a key?
35        if event.type == KEYDOWN:
36            # Was it the Escape key? If so, stop the loop.
37            if event.key == K_ESCAPE:
38                running = False
39
40        # Did the user click the window close button? If so, stop the loop.
41        elif event.type == QUIT:
42            running = False

Давайте подробнее рассмотрим этот игровой цикл:

  • Строка 28 устанавливает управляющую переменную для игрового цикла. Чтобы выйти из цикла и игры, вы устанавливаете running = False. Цикл игры начинается со строки 29.

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

  • Строки с 35 по 38 проверяют, является ли текущее event.type событием KEYDOWN. Если это так, то программа проверяет, какая клавиша была нажата, просматривая атрибут event.key. Если ключом является клавиша Esc, обозначенная как K_ESCAPE, то она выходит из игрового цикла, установив running = False.

  • Строки 41 и 42 выполняют аналогичную проверку для типа события QUIT. Это событие происходит только тогда, когда пользователь нажимает кнопку закрытия окна. Пользователь может также использовать любое другое действие операционной системы, чтобы закрыть окно.

Когда вы добавите эти строки к предыдущему коду и запустите его, вы увидите окно с пустым или черным экраном:

An empty, but persistent, pygame window

Окно не исчезнет, пока вы не нажмете клавишу Esc или иным образом не вызовете событие QUIT, закрыв окно.

Рисование на экране

В примере программы вы рисовали на экране, используя две команды:

  1. screen.fill() чтобы заполнить фон
  2. pygame.draw.circle() чтобы нарисовать круг

Теперь вы узнаете о третьем способе рисования на экране: с помощью Surface.

Напомним, что a Surface - это прямоугольный объект, на котором можно рисовать, например, на чистом листе бумаги. Объект screen - это Surface, и вы можете создавать свои собственные Surface объектов отдельно от экрана дисплея. Давайте посмотрим, как это работает:

44# Fill the screen with white
45screen.fill((255, 255, 255))
46
47# Create a surface and pass in a tuple containing its length and width
48surf = pygame.Surface((50, 50))
49
50# Give the surface a color to separate it from the background
51surf.fill((0, 0, 0))
52rect = surf.get_rect()

После того, как экран в строке 45 будет заполнен белым цветом, в строке 48 будет создан новый Surface. Этот Surface имеет ширину 50 пикселей, высоту 50 пикселей и соответствует surf. На этом этапе вы обрабатываете его так же, как и screen. Итак, в строке 51 вы заполняете его черным цветом. Вы также можете получить доступ к его базовому Rect, используя .get_rect(). Это сохраняется как rect для последующего использования.

Используя .blit() и .flip()

Простого создания нового Surface недостаточно, чтобы увидеть его на экране. Чтобы сделать это, вам нужно наложить на Surface другой Surface. Термин blit означает Перенос блока, а .blit() - это способ копирования содержимого одного Surface в другой блок. Вы можете только .blit() переключаться с одного Surface на другой, но поскольку экран - это просто еще один Surface, это не проблема. Вот как вы рисуете surf на экране:

54# This line says "Draw surf onto the screen at the center"
55screen.blit(surf, (SCREEN_WIDTH/2, SCREEN_HEIGHT/2))
56pygame.display.flip()

Вызов .blit() в строке 55 принимает два аргумента:

  1. Surface для рисования
  2. Место, в котором его нужно нарисовать на исходном коде Surface

Координаты (SCREEN_WIDTH/2, SCREEN_HEIGHT/2) указывают вашей программе на то, что она должна поместить surf точно в центр экрана, но это выглядит не совсем так:

Blitting a surface onto the screen

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

54# Put the center of surf at the center of the display
55surf_center = (
56    (SCREEN_WIDTH-surf.get_width())/2,
57    (SCREEN_HEIGHT-surf.get_height())/2
58)
59
60# Draw surf at the new coordinates
61screen.blit(surf, surf_center)
62pygame.display.flip()

Обратите внимание на звонок в pygame.display.flip() после вызова blit(). Это обновит весь экран, показав все, что было нарисовано с момента последнего переворота. Без вызова .flip() ничего не отображается.

Спрайты

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

С точки зрения программирования, спрайт - это двумерное изображение чего-либо на экране. По сути, это картинка. pygame предоставляет Sprite класс, который предназначен для хранения одного или нескольких графических представлений любого игрового объекта, который вы хотите отобразить на экране. Чтобы использовать его, вы создаете новый класс, который расширяет Sprite. Это позволяет вам использовать его встроенные методы.

Игроки

Вот как вы используете объекты Sprite в текущей игре для определения игрока. Вставьте этот код после строки 18:

20# Define a Player object by extending pygame.sprite.Sprite
21# The surface drawn on the screen is now an attribute of 'player'
22class Player(pygame.sprite.Sprite):
23    def __init__(self):
24        super(Player, self).__init__()
25        self.surf = pygame.Surface((75, 25))
26        self.surf.fill((255, 255, 255))
27        self.rect = self.surf.get_rect()

Сначала вы определяете Player, расширяя pygame.sprite.Sprite в строке 22. Затем .__init__() использует .super() для вызова .__init__() метода Sprite. Для получения дополнительной информации о том, почему это необходимо, вы можете прочитать Дополните свои классы с помощью Python super().

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

 

 1# Import the pygame module
 2import pygame
 3
 4# Import pygame.locals for easier access to key coordinates
 5# Updated to conform to flake8 and black standards
 6from pygame.locals import (
 7    K_UP,
 8    K_DOWN,
 9    K_LEFT,
10    K_RIGHT,
11    K_ESCAPE,
12    KEYDOWN,
13    QUIT,
14)
15
16# Define constants for the screen width and height
17SCREEN_WIDTH = 800
18SCREEN_HEIGHT = 600
19
20# Define a player object by extending pygame.sprite.Sprite
21# The surface drawn on the screen is now an attribute of 'player'
22class Player(pygame.sprite.Sprite):
23    def __init__(self):
24        super(Player, self).__init__()
25        self.surf = pygame.Surface((75, 25))
26        self.surf.fill((255, 255, 255))
27        self.rect = self.surf.get_rect()
28
29# Initialize pygame
30pygame.init()
31
32# Create the screen object
33# The size is determined by the constant SCREEN_WIDTH and SCREEN_HEIGHT
34screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
35
36# Instantiate player. Right now, this is just a rectangle.
37player = Player()
38
39# Variable to keep the main loop running
40running = True
41
42# Main loop
43while running:
44    # for loop through the event queue
45    for event in pygame.event.get():
46        # Check for KEYDOWN event
47        if event.type == KEYDOWN:
48            # If the Esc key is pressed, then exit the main loop
49            if event.key == K_ESCAPE:
50                running = False
51        # Check for QUIT event. If QUIT, then set running to false.
52        elif event.type == QUIT:
53            running = False
54
55    # Fill the screen with black
56    screen.fill((0, 0, 0))
57
58    # Draw the player on the screen
59    screen.blit(player.surf, (SCREEN_WIDTH/2, SCREEN_HEIGHT/2))
60
61    # Update the display
62    pygame.display.flip()

Запустите этот код. Вы увидите белый прямоугольник примерно посередине экрана:

Basic player sprite being drawn

Как вы думаете, что произойдет, если изменить строку 59 на screen.blit(player.surf, player.rect)? Попробуйте и увидите:

55# Fill the screen with black
56screen.fill((0, 0, 0))
57
58# Draw the player on the screen
59screen.blit(player.surf, player.rect)
60
61# Update the display
62pygame.display.flip()

Когда вы передаете Rect в .blit(), для рисования поверхности используются координаты верхнего левого угла. Позже вы будете использовать это, чтобы заставить игрока двигаться!

Пользовательский ввод

На данный момент вы узнали, как настроить pygame и рисовать объекты на экране. Теперь начинается самое интересное! Вы сделаете плеер управляемым с помощью клавиатуры.

Ранее вы видели, что pygame.event.get() возвращает список событий в очереди событий, которые вы проверяете на наличие KEYDOWN типов событий. Что ж, это не единственный способ считывания нажатий клавиш. pygame также предоставляет pygame.event.get_pressed(), который возвращает словарь, содержащий все текущие KEYDOWN события в очередь.

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

54# Get the set of keys pressed and check for user input
55pressed_keys = pygame.key.get_pressed()

Затем вы пишете метод в Player, который принимает этот словарь. Это будет определять поведение спрайта в зависимости от нажатых клавиш. Вот как это может выглядеть:

29# Move the sprite based on user keypresses
30def update(self, pressed_keys):
31    if pressed_keys[K_UP]:
32        self.rect.move_ip(0, -5)
33    if pressed_keys[K_DOWN]:
34        self.rect.move_ip(0, 5)
35    if pressed_keys[K_LEFT]:
36        self.rect.move_ip(-5, 0)
37    if pressed_keys[K_RIGHT]:
38        self.rect.move_ip(5, 0)

K_UP, K_DOWN, K_LEFT, и K_RIGHT соответствуют клавишам со стрелками на клавиатуре. Если словарная запись для этой клавиши равна True, то эта клавиша нажата, и вы перемещаете проигрыватель .rect в нужном направлении. Здесь вы используете .move_ip(), что означает переместить на место, для перемещения текущего Rect.

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

52# Main loop
53while running:
54    # for loop through the event queue
55    for event in pygame.event.get():
56        # Check for KEYDOWN event
57        if event.type == KEYDOWN:
58            # If the Esc key is pressed, then exit the main loop
59            if event.key == K_ESCAPE:
60                running = False
61        # Check for QUIT event. If QUIT, then set running to false.
62        elif event.type == QUIT:
63            running = False
64
65    # Get all the keys currently pressed
66    pressed_keys = pygame.key.get_pressed()
67
68    # Update the player sprite based on user keypresses
69    player.update(pressed_keys)
70
71    # Fill the screen with black
72    screen.fill((0, 0, 0))

Теперь вы можете перемещать прямоугольник вашего плеера по экрану с помощью клавиш со стрелками:

Keypresses moving a sprite in pygame

Вы можете заметить две небольшие проблемы:

  1. Прямоугольник игрока может перемещаться очень быстро, если удерживать нажатой клавишу. Вы поработаете над этим позже.
  2. Прямоугольник игрока может перемещаться за пределы экрана. Давайте решим этот вопрос сейчас.

Чтобы игрок оставался на экране, вам нужно добавить некоторую логику для определения того, собирается ли rect переместиться за пределы экрана. Для этого вы проверяете, не переместились ли координаты rect за пределы экрана. Если это так, то вы даете команду программе переместить их обратно к краю:

25# Move the sprite based on user keypresses
26def update(self, pressed_keys):
27    if pressed_keys[K_UP]:
28        self.rect.move_ip(0, -5)
29    if pressed_keys[K_DOWN]:
30        self.rect.move_ip(0, 5)
31    if pressed_keys[K_LEFT]:
32        self.rect.move_ip(-5, 0)
33    if pressed_keys[K_RIGHT]:
34        self.rect.move_ip(5, 0)
35
36    # Keep player on the screen
37    if self.rect.left < 0:
38        self.rect.left = 0
39    if self.rect.right > SCREEN_WIDTH:
40        self.rect.right = SCREEN_WIDTH
41    if self.rect.top <= 0:
42        self.rect.top = 0
43    if self.rect.bottom >= SCREEN_HEIGHT:
44        self.rect.bottom = SCREEN_HEIGHT

Здесь, вместо использования .move(), вы просто меняете соответствующие координаты .top, .bottom, .left, или .right напрямую. Проверьте это, и вы обнаружите, что прямоугольник игрока больше не может перемещаться за пределы экрана.

Теперь давайте добавим немного врагов!

Враги

Что это за игра без врагов? Вы будете использовать уже изученные приемы, чтобы создать базовый класс врагов, а затем создать множество таких, которых ваш игрок будет избегать. Сначала импортируем библиотеку random:

 4# Import random for random numbers
 5import random

Затем создайте новый класс спрайтов с именем Enemy, следуя тому же шаблону, который вы использовали для Player:

55# Define the enemy object by extending pygame.sprite.Sprite
56# The surface you draw on the screen is now an attribute of 'enemy'
57class Enemy(pygame.sprite.Sprite):
58    def __init__(self):
59        super(Enemy, self).__init__()
60        self.surf = pygame.Surface((20, 10))
61        self.surf.fill((255, 255, 255))
62        self.rect = self.surf.get_rect(
63            center=(
64                random.randint(SCREEN_WIDTH + 20, SCREEN_WIDTH + 100),
65                random.randint(0, SCREEN_HEIGHT),
66            )
67        )
68        self.speed = random.randint(5, 20)
69
70    # Move the sprite based on speed
71    # Remove the sprite when it passes the left edge of the screen
72    def update(self):
73        self.rect.move_ip(-self.speed, 0)
74        if self.rect.right < 0:
75            self.kill()

Есть четыре заметных различия между Enemy и Player:

  1. В строках с 62 по 67 вы изменяете rect на случайное расположение вдоль правого края экрана. Центр прямоугольника находится за пределами экрана. Он расположен на расстоянии от 20 до 100 пикселей от правого края и где-то между верхним и нижним краями.

  2. В строке 68 вы определяете .speed как случайное число от 5 до 20. Это указывает на то, насколько быстро этот враг приближается к игроку.

  3. В строках с 73 по 76 вы определяете .update(). Аргументы не требуются, поскольку враги перемещаются автоматически. Вместо этого .update() перемещает противника в левую часть экрана на .speed, определенное при его создании.

  4. В строке 74 вы проверяете, ушел ли противник за пределы экрана. Чтобы убедиться, что Enemy полностью исчезнет с экрана и не исчезнет просто так, пока он еще виден, вы проверяете, что правая часть .rect вышла за пределы левой части экрана. Как только враг исчезнет с экрана, вы вызываете .kill(), чтобы предотвратить дальнейшую обработку.

Итак, что же делает .kill()? Чтобы разобраться в этом, вам нужно знать о Группах спрайтов.

Группы спрайтов

Еще один очень полезный класс, который предоставляет pygame, - это Sprite Group. Это объект, содержащий группу из Sprite объектов. Так зачем его использовать? Разве вы не можете просто отслеживать свои Sprite объектов в списке? Что ж, вы можете, но преимущество использования Group заключается в методах, которые оно предоставляет. Эти методы помогают определить, не столкнулся ли какой-либо Enemy с Player, что значительно упрощает обновление.

Давайте посмотрим, как создавать группы спрайтов. Вы создадите два разных объекта Group:

  1. Первые Group будут проходить каждые Sprite в игре.
  2. Второй Group будет содержать только Enemy объектов.

Вот как это выглядит в коде:

82# Create the 'player'
83player = Player()
84
85# Create groups to hold enemy sprites and all sprites
86# - enemies is used for collision detection and position updates
87# - all_sprites is used for rendering
88enemies = pygame.sprite.Group()
89all_sprites = pygame.sprite.Group()
90all_sprites.add(player)
91
92# Variable to keep the main loop running
93running = True

Когда вы вызываете .kill(), Sprite удаляется из каждого Group, к которому он принадлежит. При этом также удаляются ссылки на Sprite, что позволяет сборщику мусора Python освобождать память по мере необходимости.

Теперь, когда у вас есть группа all_sprites, вы можете изменить способ отображения объектов. Вместо вызова .blit() только для Player, вы можете выполнить итерацию всего в all_sprites:

117# Fill the screen with black
118screen.fill((0, 0, 0))
119
120# Draw all sprites
121for entity in all_sprites:
122    screen.blit(entity.surf, entity.rect)
123
124# Flip everything to the display
125pygame.display.flip()

Теперь все, что помещено в all_sprites, будет отображаться с каждым кадром, будь то враг или игрок.

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

Пользовательские события

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

  1. Создайте новый Enemy.
  2. Добавьте его в all_sprites и enemies.

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

Давайте посмотрим, как создать пользовательское событие, которое генерируется каждые несколько секунд. Вы можете создать пользовательское событие, назвав его:

78# Create the screen object
79# The size is determined by the constant SCREEN_WIDTH and SCREEN_HEIGHT
80screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
81
82# Create a custom event for adding a new enemy
83ADDENEMY = pygame.USEREVENT + 1
84pygame.time.set_timer(ADDENEMY, 250)
85
86# Instantiate player. Right now, this is just a rectangle.
87player = Player()

pygame определяет события внутренне как целые числа, поэтому вам нужно определить новое событие с уникальным целым числом. Последнее событие pygame резервов называется USEREVENT, поэтому определение ADDENEMY = pygame.USEREVENT + 1 в строке 83 гарантирует его уникальность.

Далее вам нужно вставлять это новое событие в очередь событий через регулярные промежутки времени на протяжении всей игры. Вот тут-то и пригодится модуль time. Строка 84 запускает новое событие ADDENEMY каждые 250 миллисекунд, или четыре раза в секунду. Вы вызываете .set_timer() вне игрового цикла, поскольку вам нужен только один таймер, но он будет срабатывать на протяжении всей игры.

Добавьте код для обработки вашего нового события:

100# Main loop
101while running:
102    # Look at every event in the queue
103    for event in pygame.event.get():
104        # Did the user hit a key?
105        if event.type == KEYDOWN:
106            # Was it the Escape key? If so, stop the loop.
107            if event.key == K_ESCAPE:
108                running = False
109
110        # Did the user click the window close button? If so, stop the loop.
111        elif event.type == QUIT:
112            running = False
113
114        # Add a new enemy?
115        elif event.type == ADDENEMY:
116            # Create the new enemy and add it to sprite groups
117            new_enemy = Enemy()
118            enemies.add(new_enemy)
119            all_sprites.add(new_enemy)
120
121    # Get the set of keys pressed and check for user input
122    pressed_keys = pygame.key.get_pressed()
123    player.update(pressed_keys)
124
125    # Update enemy position
126    enemies.update()

Всякий раз, когда обработчик событий видит новое событие ADDENEMY в строке 115, он создает Enemy и добавляет его к enemies и all_sprites. Поскольку Enemy находится в all_sprites, он будет отображаться в каждом кадре. Вам также нужно вызвать enemies.update() по строке 126, которая обновит все данные в enemies, чтобы убедиться, что они перемещаются правильно:

Enemies flying by in pygame

Однако это не единственная причина, по которой существует группа только для enemies.

Обнаружение столкновений

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

Вот где пригодится такой фреймворк, как pygame! Написание кода для обнаружения коллизий утомительно, но в pygame есть множество методов обнаружения коллизий, которые вы можете использовать.

В этом руководстве вы будете использовать метод под названием .spritecollideany(),, который читается как “столкновение спрайтов с любым”. Этот метод принимает Sprite и Group в качестве параметров. Он просматривает каждый объект в Group и проверяет, пересекается ли его .rect с .rect из Sprite. Если это так, то он возвращает True. В противном случае он возвращает False. Это идеально подходит для данной игры, так как вам нужно проверить, не столкнется ли одиночный player с одним из Group из enemies.

Вот как это выглядит в коде:

130# Draw all sprites
131for entity in all_sprites:
132    screen.blit(entity.surf, entity.rect)
133
134# Check if any enemies have collided with the player
135if pygame.sprite.spritecollideany(player, enemies):
136    # If so, then remove the player and stop the loop
137    player.kill()
138    running = False

Строка 135 проверяет, не столкнулся ли player с каким-либо из объектов в enemies. Если это так, то вызывается player.kill(), чтобы удалить его из каждой группы, к которой он принадлежит. Поскольку единственные отображаемые объекты находятся в all_sprites, player больше не будут отображаться. Как только игрок будет убит, вам также необходимо выйти из игры, поэтому вы устанавливаете running = False для выхода из игрового цикла в строке 138.

На данный момент у вас есть основные элементы игры:

Pygame window

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

Изображения спрайтов

Ладно, у вас есть игра, но давайте будем честны… Она довольно уродлива. Игрок и враги - это просто белые блоки на черном фоне. Это было по последнему слову техники, когда Pong был в новинку, но теперь это просто не работает. Давайте заменим все эти скучные белые прямоугольники более крутыми изображениями, которые сделают игру похожей на настоящую игру.

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

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

Изменение конструкторов объектов

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

 7# Import pygame.locals for easier access to key coordinates
 8# Updated to conform to flake8 and black standards
 9# from pygame.locals import *
10from pygame.locals import (
11    RLEACCEL,
12    K_UP,
13    K_DOWN,
14    K_LEFT,
15    K_RIGHT,
16    K_ESCAPE,
17    KEYDOWN,
18    QUIT,
19)
20
21# Define constants for the screen width and height
22SCREEN_WIDTH = 800
23SCREEN_HEIGHT = 600
24
25
26# Define the Player object by extending pygame.sprite.Sprite
27# Instead of a surface, use an image for a better-looking sprite
28class Player(pygame.sprite.Sprite):
29    def __init__(self):
30        super(Player, self).__init__()
31        self.surf = pygame.image.load("jet.png").convert()
32        self.surf.set_colorkey((255, 255, 255), RLEACCEL)
33        self.rect = self.surf.get_rect()

Давайте немного распакуем строку 31. pygame.image.load() загружается изображение с диска. Вы передаете ему путь к файлу. Он возвращает Surface, а вызов .convert() оптимизирует Surface, ускоряя будущие вызовы .blit().

В строке 32 используется .set_colorkey() для указания цвета, который pygame будет отображаться как прозрачный. В данном случае вы выбираете белый, потому что это цвет фона изображения jet. Константа RLEACCEL является необязательным параметром, который помогает pygame быстрее выполнять рендеринг на дисплеях без ускорения. Это добавляется к инструкции pygame.locals import в строке 11.

Больше ничего менять не нужно. Изображение по-прежнему является Surface, за исключением того, что теперь на нем нарисована картинка. Вы по-прежнему используете его таким же образом.

Вот как выглядят аналогичные изменения в Enemy:

59# Define the enemy object by extending pygame.sprite.Sprite
60# Instead of a surface, use an image for a better-looking sprite
61class Enemy(pygame.sprite.Sprite):
62    def __init__(self):
63        super(Enemy, self).__init__()
64        self.surf = pygame.image.load("missile.png").convert()
65        self.surf.set_colorkey((255, 255, 255), RLEACCEL)
66        # The starting position is randomly generated, as is the speed
67        self.rect = self.surf.get_rect(
68            center=(
69                random.randint(SCREEN_WIDTH + 20, SCREEN_WIDTH + 100),
70                random.randint(0, SCREEN_HEIGHT),
71            )
72        )
73        self.speed = random.randint(5, 20)

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

Добавление фоновых изображений

Для фоновых облаков вы используете те же принципы, что и для Player и Enemy:

  1. Создайте класс Cloud.
  2. Добавьте в него изображение облака.
  3. Создайте метод .update(), который перемещает cloud в левую часть экрана.
  4. Создайте пользовательское событие и обработчик для создания новых cloud объектов с заданным интервалом времени.
  5. Добавьте вновь созданные cloud объектов в новый Group, называемый clouds.
  6. Обновите и нарисуйте clouds в вашем игровом цикле.

Вот как выглядит Cloud:

 83# Define the cloud object by extending pygame.sprite.Sprite
 84# Use an image for a better-looking sprite
 85class Cloud(pygame.sprite.Sprite):
 86    def __init__(self):
 87        super(Cloud, self).__init__()
 88        self.surf = pygame.image.load("cloud.png").convert()
 89        self.surf.set_colorkey((0, 0, 0), RLEACCEL)
 90        # The starting position is randomly generated
 91        self.rect = self.surf.get_rect(
 92            center=(
 93                random.randint(SCREEN_WIDTH + 20, SCREEN_WIDTH + 100),
 94                random.randint(0, SCREEN_HEIGHT),
 95            )
 96        )
 97
 98    # Move the cloud based on a constant speed
 99    # Remove the cloud when it passes the left edge of the screen
100    def update(self):
101        self.rect.move_ip(-5, 0)
102        if self.rect.right < 0:
103            self.kill()

Все это должно выглядеть очень знакомо. Это почти то же самое, что Enemy.

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

116# Create custom events for adding a new enemy and a cloud
117ADDENEMY = pygame.USEREVENT + 1
118pygame.time.set_timer(ADDENEMY, 250)
119ADDCLOUD = pygame.USEREVENT + 2
120pygame.time.set_timer(ADDCLOUD, 1000)

Здесь указано, что необходимо подождать 1000 миллисекунд или одну секунду, прежде чем создавать следующий cloud.

Затем создайте новый Group для хранения каждого вновь созданного cloud:

125# Create groups to hold enemy sprites, cloud sprites, and all sprites
126# - enemies is used for collision detection and position updates
127# - clouds is used for position updates
128# - all_sprites is used for rendering
129enemies = pygame.sprite.Group()
130clouds = pygame.sprite.Group()
131all_sprites = pygame.sprite.Group()
132all_sprites.add(player)

Затем добавьте обработчик для нового события ADDCLOUD в обработчик событий:

137# Main loop
138while running:
139    # Look at every event in the queue
140    for event in pygame.event.get():
141        # Did the user hit a key?
142        if event.type == KEYDOWN:
143            # Was it the Escape key? If so, then stop the loop.
144            if event.key == K_ESCAPE:
145                running = False
146
147        # Did the user click the window close button? If so, stop the loop.
148        elif event.type == QUIT:
149            running = False
150
151        # Add a new enemy?
152        elif event.type == ADDENEMY:
153            # Create the new enemy and add it to sprite groups
154            new_enemy = Enemy()
155            enemies.add(new_enemy)
156            all_sprites.add(new_enemy)
157
158        # Add a new cloud?
159        elif event.type == ADDCLOUD:
160            # Create the new cloud and add it to sprite groups
161            new_cloud = Cloud()
162            clouds.add(new_cloud)
163            all_sprites.add(new_cloud)

Наконец, убедитесь, что clouds обновляется каждый кадр:

167# Update the position of enemies and clouds
168enemies.update()
169clouds.update()
170
171# Fill the screen with sky blue
172screen.fill((135, 206, 250))

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

Обратите внимание, что все новые Cloud и Enemy добавляются к all_sprites, а также к clouds и enemies. Это сделано потому, что каждая группа используется для отдельной цели:

  • Рендеринг выполняется с помощью all_sprites.
  • Обновление позиции выполняется с помощью clouds и enemies.
  • Обнаружение столкновений осуществляется с помощью enemies.

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

Скорость игры

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

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

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

Использование Clock для настройки воспроизводимой частоты кадров требует всего двух строк кода. Первая строка создает новый Clock перед началом игрового цикла:

106# Setup the clock for a decent framerate
107clock = pygame.time.Clock()

Второй вызывает .tick(), чтобы сообщить pygame, что программа достигла конца кадра:

188# Flip everything to the display
189pygame.display.flip()
190
191# Ensure program maintains a rate of 30 frames per second
192clock.tick(30)

Аргумент, передаваемый в .tick(), устанавливает желаемую частоту кадров. Для этого .tick() вычисляет количество миллисекунд, которое должен занимать каждый кадр, исходя из желаемой частоты кадров. Затем он сравнивает это число с количеством миллисекунд, прошедших с момента последнего вызова .tick(). Если прошло недостаточно времени, то .tick() задерживает обработку, чтобы гарантировать, что она никогда не превысит заданную частоту кадров.

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

Setting the frame rate in pygame

Поиграйте с этим числом, чтобы понять, что вам больше подходит!

Звуковые эффекты

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

Название mixer указывает на то, что модуль смешивает различные звуки в единое целое. Используя подмодуль music, вы можете передавать отдельные звуковые файлы в различных форматах, таких как MP3, Ogg и Mod. Вы также можете использовать Sound для сохранения одного звукового эффекта для воспроизведения в форматах Ogg или без сжатия WAV. Все воспроизведение происходит в фоновом режиме, поэтому при воспроизведении Sound метод возвращается сразу же после воспроизведения звука.

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

Как и в большинстве случаев pygame, использование mixer начинается с шага инициализации. К счастью, это уже обработано pygame.init(). Вам нужно только вызвать pygame.mixer.init(), если вы хотите изменить значения по умолчанию:

106# Setup for sounds. Defaults are good.
107pygame.mixer.init()
108
109# Initialize pygame
110pygame.init()
111
112# Set up the clock for a decent framerate
113clock = pygame.time.Clock()

pygame.mixer.init() принимает ряд аргументов, но значения по умолчанию в большинстве случаев работают нормально. Обратите внимание, что если вы хотите изменить значения по умолчанию, вам необходимо вызвать pygame.mixer.init() перед вызовом pygame.init(). В противном случае значения по умолчанию будут действовать независимо от внесенных вами изменений.

После инициализации системы вы можете настроить звуки и фоновую музыку:

135# Load and play background music
136# Sound source: http://ccmixter.org/files/Apoxode/59262
137# License: https://creativecommons.org/licenses/by/3.0/
138pygame.mixer.music.load("Apoxode_-_Electric_1.mp3")
139pygame.mixer.music.play(loops=-1)
140
141# Load all sound files
142# Sound sources: Jon Fincher
143move_up_sound = pygame.mixer.Sound("Rising_putter.ogg")
144move_down_sound = pygame.mixer.Sound("Falling_putter.ogg")
145collision_sound = pygame.mixer.Sound("Collision.ogg")

Строки 138 и 139 загружают фоновый звуковой клип и начинают его воспроизведение. Вы можете сделать звуковой клип циклическим и никогда не заканчивающимся, установив именованный параметр loops=-1.

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

Итак, как вы используете звуковые эффекты? Вы хотите воспроизводить каждый звук при наступлении определенного события. Например, когда корабль поднимается, вы хотите воспроизвести move_up_sound. Следовательно, вы добавляете вызов в .play() всякий раз, когда обрабатываете это событие. В проекте это означает добавление следующих вызовов в .update() для Player:

26# Define the Player object by extending pygame.sprite.Sprite
27# Instead of a surface, use an image for a better-looking sprite
28class Player(pygame.sprite.Sprite):
29    def __init__(self):
30        super(Player, self).__init__()
31        self.surf = pygame.image.load("jet.png").convert()
32        self.surf.set_colorkey((255, 255, 255), RLEACCEL)
33        self.rect = self.surf.get_rect()
34
35    # Move the sprite based on keypresses
36    def update(self, pressed_keys):
37        if pressed_keys[K_UP]:
38            self.rect.move_ip(0, -5)
39            move_up_sound.play()
40        if pressed_keys[K_DOWN]:
41            self.rect.move_ip(0, 5)
42            move_down_sound.play()

При обнаружении столкновения между игроком и противником воспроизводится звуковой сигнал:

201# Check if any enemies have collided with the player
202if pygame.sprite.spritecollideany(player, enemies):
203    # If so, then remove the player
204    player.kill()
205
206    # Stop any moving sounds and play the collision sound
207    move_up_sound.stop()
208    move_down_sound.stop()
209    collision_sound.play()
210
211    # Stop the loop
212    running = False

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

Наконец, по окончании игры все звуки должны прекратиться. Это верно независимо от того, завершается ли игра из-за столкновения или пользователь выходит вручную. Для этого добавьте следующие строки в конце программы после цикла:

220# All done! Stop and quit the mixer.
221pygame.mixer.music.stop()
222pygame.mixer.quit()

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

Вот и все! Проверьте это еще раз, и вы должны увидеть что-то вроде этого:

Pygame window

Примечание к источникам

Возможно, вы заметили комментарий к строкам 136-137 при загрузке фоновой музыки, в котором указан источник музыки и ссылка на лицензию Creative Commons. Это было сделано потому, что этого требовал создатель этого звука. В лицензионных требованиях указано, что для использования звука должны быть указаны как надлежащие авторские права, так и ссылка на лицензию.

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

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

Заключение

Из этого урока вы узнали, чем программирование игр с помощью pygame отличается от стандартного процедурного программирования. Вы также узнали, как:

  • Реализовывать циклы событий
  • Рисовать элементы на экране
  • Воспроизведение звуковых эффектов и музыки
  • Обработка пользовательского ввода

Для этого вы использовали подмножество модулей pygame, включая модули display, mixer и music, time, image, event, и key. Вы также использовали несколько классов pygame, включая Rect, Surface, Sound, и Sprite. Но это лишь малая часть того, что может сделать pygame! Ознакомьтесь с официальной pygame документацией для получения полного списка доступных модулей и классов.

Back to Top