Создание видеоигр с помощью PyGame [Майк Голд] (pdf) читать онлайн

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]
  [Оглавление]

Создание видеоигр с
помощью PyGame
С пошаговыми примерами

Майк Голд

2023

Оглавление
https://t.me/it_boooks/2
Настройка Python и Pygame . . . . . . . . . . . . . . . .
1
Начало старта. . . . . . . . . . . . . . . . . . . . . . 1.
Установка Pygame . . . . . . . . . . . . . . . . . . . . 3.
Введение в Python . . . . . . . . . . . . . . . . . . . . . 5.
Введение в PyGame . . . . . . . . . . . . . . . . . . . 24
. .
Мигание Hello World . . . . . . . . . . . . . . . . . . .
28
Ответ на клавиатуру . . . . . . . . . . . . . . . . . . .
34
Заключение . . . . . . . . . . . . . . . . . . . . . . . . . 38
Крестики-нолики в PyGame . . . . . . . . . . . . . . . .
Вступление . . . . . . . . . . . . . . . . . . . . . . . . . .
Основной цикл . . . . . . . . . . . . . . . . . . . . . . .
Обработка событий . . . . . . . . . . . . . . . . . . .
Создание доски . . . . . . . . . . . . . . . . . . . . .
Лучший ИИ . . . . . . . . . . . . . . . . . . . . . . . . .
Заключение . . . . . . . . . . . . . . . . . . . . . . . . .

39
39
40
41
42
50
54

Использование классов в Pygame . . . . . . . . . .
Введение . . . . . . . . . . . . . . . . . . . . . . . . . .

55
55

Рефакторинг игровой логики . . . . . . . . . . . . .
Заключение . . . . . . . . . . . . . . . . . . . . . . . . .

60
68

Глава 6 - Пожиратель камней . . . . . . . . . . . . .
Введение . . . . . . . . . . . . . . . . . . . . . . . . . .

70
70

TABLE OF CONTENTS

Проект игры . . . . . . . . . . . . . . . . . . . . . . . .
Обнаружение нажатия клавиш . . . . . . . . . . .

71
77

Space Invasion в PyGame . . . . . . . . . . . . . . . . . . .
Введение . . . . . . . . . . . . . . . . . . . . . . . . . .
Цель игр . . . . . . . . . . . . . . . . . . . . . . . . . .
Основной цикл . . . . . . . . . . . . . . . . . . . . . .
Игровые спрайты . . . . . . . . . . . . . . . . . . . . .
Спрайт захватчика. . . . . . . . . . . . . . . . . . .
Спрайт пули. . . . . . . . . . . . . . . . . . . . . . . .
Спрайт бомбы. . . . . . . . . . . . . . . . . . . . . .
Перемещение игрока . . . . . . . . . . . . . . . . . .
Стрельба пулей . . . . . . . . . . . . . . . . . . . . . .
Проверка на попадание инопланетян . . . . . . .
Рисуем пришельцев . . . . . . . . . . . . . . . . . . .
Добавление в Scoring . . . . . . . . . . . . . . . . . . .
Запуск НЛО . . . . . . . . . . . . . . . . . . . . . . . . . .
Заключение . . . . . . . . . . . . . . . . . . . . . . . . .

92
92
93
95
97
101
104
105
107
110
112
114
125
129
137

Приложение . . . . . . . . . . . . . . . . . . . . . . . . .
Исходный код . . . . . . . . . . . . . . . . . . . . . . .
Где найти изображения . . . . . . . . . . . . . . . . .
Где найти звуки . . . . . . . . . . . . . . . . . . . . . .
Другие источники . . . . . . . . . . . . . . . . . . . . .

138
138
138
138
139

Настройка Python и
Pygame
Добро пожаловать в мир программирования PyGame и Python! Эта
книга познакомит вас с библиотекой PyGame и научит вас
создавать собственные игры с использованием языка Python. Мы
начнем с базового обзора Python и библиотеки PyGame, а затем
перейдем к разработке, написанию и отладке нашей собственной
игры. От добавления графики и звуков до создания анимации и
бонусов — мы расскажем обо всем, что вам нужно знать, чтобы
создать собственную насыщенную интерактивную игру. Наконец,
мы пройдем процесс отладки и тестирования нашей игры, прежде
чем опубликовать ее для всего мира. Итак, давайте начнем и
научимся создавать собственные игры с помощью PyGame и
Python!

Начало старта
Установка Python
Вы можете найти последнюю версию Python на сайте Python.org¹.
Доступны как 32-битные, так и 64-битные версии. После того, как вы
нажали кнопку «Загрузить», запустите загруженный исполняемый
файл, следуя инструкциям, чтобы установить последнюю версию
Python на свой компьютер.

¹https://www.python.org/downloads/

Настройка Python и Pygame

2

Установка VSCode
Visual Studio Code доступен для операционных систем Windows,
MacOS, Linux. Вы можете загрузить код Visual Studio с
https://code.visualstudio.com/download. Выберите соответствующую
загрузку для вашей ОС и запустите установку. После того, как вы
установили Visual Studio Code, вам нужно установить расширения
Python и Pylance.

Расширение Python:
Расширение Python для Visual Studio Code предоставляет широкий
спектр функций, упрощающих разработку Python в VS Code,
включая анализ кода, отладку, завершение кода IntelliSense,
форматирование кода, рефакторинг, модульное тестирование и
многое другое. Расширение имеет открытый исходный код и
доступно бесплатно, и его можно установить, выполнив поиск на
рынке расширений VS Code. С расширением Python разработчики
могут быстро и легко создавать свои проекты Python и управлять
ими, а также использовать широкий спектр расширенных функций.
Расширение Pylance:
Pylance — это расширение Visual Studio Code, обеспечивающее
расширенную поддержку языка Python, включая быстрый
многофункциональный IntelliSense, линтинг, анализ всего проекта и
отладку. Pylance использует протокол языкового сервера (LSP) для
связи с языковым сервером и поддерживает широкий спектр
функций, таких как автозаполнение, рефакторинг кода, навигация
по коду и диагностика ошибок. Pylance также предоставляет
функцию автоматического импорта, которая может автоматически
добавлять импорт для символов, когда вы вводите их в свой код.
Pylance — отличный инструмент для разработчиков Python,
позволяющий быстро и эффективно писать код.
Чтобы установить расширения, перейдите к символу расширений
на левой панели Visual Studio Code и выполните поиск Pylance на
торговой площадке. Нажмите на него и установите расширение в
код VisualStudio. Также найдите расширение под названием Python
и установите его.

Настройка Python и Pygame

Установка Pygame
Pygame — это библиотека с открытым исходным кодом для
создания игр на Python. Она имеет широкий спектр возможностей и
функций, которые облегчают начало создания игр.
Вы можете найти документацию по Pygame на pygame.org².
Чтобы начать использовать Pygame, вам необходимо установить
его. Самый простой способ установить pygame — с терминала
внутри VSCode. Щелкните терминал в верхней части меню и
введите следующую строку:
pip install pygame
если у вас еще не установлен pip, вам нужно будет перейти на
https://bootstrap.pypa.io/get-pip.py и загрузить файл в каталог вашего
приложения python. Чтобы выяснить, где установлен python, вы
можете спросить python! Перейдите к терминалу в Visual Code и
введите
1

python

Вы увидите подсказку >>>. Вставьте следующий код

1
2
3

>>> import os
>>> import sys
>>> os.path.dirname(sys.executable)

Это выдаст путь, по которому вы разместите файл get-pip.py.
например C:\Python310 в Windows
Поместите get-pip.py в указанный путь, а затем запустите
²http://www.pygame.org/docs/

3

Настройка Python и Pygame

1

4

py get-pip.py

� Примечание. Возможно, вам потребуется добавить
путь python к пути к переменным среды.
Два пути, которые у меня есть, показаны ниже

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

История Python
Python был создан Гвидо ван Россумом и впервые выпущен в 1991
году. Python — это высокоуровневый интерпретируемый язык
программирования общего назначения. Python стал популярным
благодаря понятному синтаксису и удобочитаемости. Python также
поддерживает модули и пакеты, что позволяет повторно
использовать код.
Python является интерпретируемым языком, что означает, что он
компилируется во время выполнения. Это позволяет коду Python
быть более терпимым к ошибкам и упрощает отладку. Python также
поддерживает ряд систем и сред с открытым исходным кодом,
таких как Django и Flask.
Python часто используется для научных вычислений,
веб-разработки, машинного обучения и автоматизации. Python
имеет большое и активное сообщество, что упрощает поиск
помощи и поддержки в Интернете. Python используется такими
организациями, как Google, Yahoo и NASA.

Введение в Python

Что отличает Python от других языков?
Python — это интерпретируемый язык, поэтому начать работу с ним
проще, чем с другими языками, такими как C или Java. Он также
динамически типизирован, то есть вам не нужно объявлять тип при
создании переменной. Это делает язык более выразительным и
может уменьшить сложность некоторых приложений. Python также
обладает высокой расширяемостью, что означает, что его можно
расширять за счет существующих библиотек и новых модулей,
написанных на C, C++ или других языках. Кроме того, синтаксис
Python относительно прост и легок в освоении.

Какие типы приложений создаются с
помощью Python?
Python используется в самых разных приложениях, включая
настольные приложения с графическим интерфейсом
пользователя, веб-приложения, разработку программного
обеспечения, научные и числовые вычисления, а также
искусственный интеллект и машинное обучение. Многие из самых
популярных веб-сайтов и сервисов, таких как YouTube, Instagram,
Quora и Dropbox, были созданы с использованием Python.

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

6

Введение в Python

7

Python — это язык с открытым исходным кодом, что означает, что
его можно использовать бесплатно и он доступен для всех, у кого
есть доступ в Интернет.
Теперь, когда вы немного знаете об этом языке, давайте создадим
нашу первую программу на Python, просто чтобы намочить ноги. Мы
собираемся прыгнуть в python.
Давайте начнем с простой программы, которая печатает Hello World:
Создайте новую папку в VSCode под названием HelloWorld. Затем
создайте новый файл с именем HelloWorld.py и добавьте
следующую строку:
1

print("Hello World")

Сохраните файл. Перейдите к своему терминалу в VSCode
(терминал bash) и запустите python с помощью следующей команды:
1

py -m HelloWorld

Вы должны увидеть следующий вывод в окне терминала:
1

Hello World

Неплохо! Если вы зашли так далеко, вы готовы к работе. Давайте
немного поднимем циферблат. Давайте напишем программу,
которая напишет Hello World 10 раз. Для этого мы будем
использовать цикл for. Цикл for позволяет нам перебирать диапазон
значений и каждый раз в цикле выводить «Hello World». Измените
свой файл HelloWorld.py на приведенный ниже код и запустите.

1

for number in range(5):
print ('Hello World')

2

Эта программа производит следующий вывод

Введение в Python

1
2
3
4
5

Hello
Hello
Hello
Hello
Hello

8

World
World
World
World
World

Обратите внимание, что внутри нашего цикла for есть число.
Каждый раз, когда число проходит через цикл, оно увеличивается
до следующего числа. Мы можем показать это в нашем операторе
печати, используя интерполяцию строк. Измените HelloWorld.py на
этот код:
1
2

for number in range(5):
print (f'Hello World #{number}')

Эта программа производит этот вывод после запуска:
1
2
3
4
5

Hello
Hello
Hello
Hello
Hello

World
World
World
World
World

#0
#1
#2
#3
#4

Обратите внимание, что функция диапазона начинается с 0 и
заканчивается на 4, а не на 5. Если бы мы хотели, чтобы наш hello
world считался до пяти, мы могли бы просто добавить единицу к
числу.
1
2

for number in range(5):
print (f'Hello World #{number+1}')

Он производит выходные данные, которые нумеруют Hello World 1-5:

9

Введение в Python

1
2
3
4
5

Hello
Hello
Hello
Hello
Hello

World
World
World
World
World

#1
#2
#3
#4
#5

Оператор если
Что, если бы мы хотели распечатать только «Hello World’s»? Теперь
мы можем ввести оператор if, который позволяет нам принимать
решения о том, какой из Hello Worlds будет напечатан.

1
2
3
4

for number in range(5):
numberToPrint = number + 1
if numberToPrint % 2 == 0:
print (f'Even Hello World #{numberToPrint}')

Этот код вводит оператор if для принятия решений. В этом случае
оператор if использует функцию модификатора (%), чтобы
определить, есть ли какие-либо остатки при делении следующего
числа на 2. Если остаток от деления numberToPrint на 2 равен нулю,
печать будет выполнена. Так, например, 2 % 2 не имеет остатков,
поэтому он проходит мод-тест numberToPrint % 2 == 0 и напечатает
Even Hello World #2. С другой стороны, 5 % 2 равно 1, поэтому оно не
проходит тест на равенство 0, поскольку 0 не равно 1. Печать будет
пропущена для 5.
Таким образом, после запуска программы код напечатает «Even
Hello World #2», «Even Hello World #4». Он не будет печатать «Even
Hello World #1», «Even Hello World #3» и «Even Hello World #5»,
поскольку ни одно из этих чисел не является четным и не
соответствует критериям функции mod.

10

Введение в Python

1
2

Even Hello World #2
Even Hello World #4

Оператор else
если мы хотим получить более полный ответ на распечатку нашего
четного числа, мы также можем напечатать, является ли число
четным или нечетным, мы будем использовать оператор else, чтобы
помочь нам здесь:
1
2
3
4
5
6

for number in range(5):
numberToPrint = number + 1
if (numberToPrint) % 2 == 0:
print (f'Even Hello World #{numberToPrint}')
else:
print (f'Odd Hello World #{numberToPrint}')

Оператор else выполняется, когда условие в операторе if ложно. Он
используется для выполнения другого кода, когда условие не
выполняется. В приведенном выше примере оператор else выводит
сообщение со словом Odd Hello World #{numberToPrint}, если число
нечетное.

1
2
3
4
5

Odd Hello World #1
Even Hello World #2
Odd Hello World #3
Even Hello World #4
Odd Hello World #5

elif
В Python оператор elif (сокращение от «else if») — это условный
оператор, который позволяет вам проверять несколько
выражений на TRUE - ИСТИНА и выполнять блок кода, как только
одно из условий оценивается как TRUE. Оператор elif следует
тому же синтаксису, что и оператор if, но с одним
дополнительным ключевым словом: elif.

11

Введение в Python

Например, следующий код проверит, делится ли numberToPrint на 3,
а если нет, то проверит, является ли numberToPrint четным. Если ни
одно из них не верно, сработает else и напечатает, что оно не четное
и не делится на 3:
1
2
3
4
5
6
7
8
9

for number in range(5):
numberToPrint = number + 1
if numberToPrint % 3 == 0:
print (f'{numberToPrint} is divisible by 3')
elif numberToPrint % 2 == 0:
print (f'{numberToPrint} is even')
else:
print (f'{numberToPrint}
Not even and not divisible by 3')

Вот вывод кода, иллюстрирующий, как работает if else:
1

1 Не четное и не делится на 3

2

2 четно

3

3 делится на 3

4

4 четно

5

5 Не четное и не делится на 3

Цикл while
Цикл while дает нам большую гибкость при переборе данных:
Иногда это дает нам слишком много гибкости! Следующий цикл
while будет работать вечно:

Введение в Python

1
2

12

while True:
print('Hello World')

Здесь условие всегда истинно, поэтому оно никогда не завершит
цикл. Циклы while заканчиваются, когда условие после while
становится ложным. Другой способ выйти из цикла — использовать
оператор break:
1
2
3

while True:
print('Hello World')
break

Выход для этого цикла:
1

Hello World

потому что программа по-прежнему войдет в цикл while и
напечатает «Hello World», но сразу после того, как она нажмет
оператор печати, она нажмет на разрыв, что приведет к выходу из
цикла.
Мы можем показать мощь цикла while, переписав цикл for выше:

1
2
3
4

number = 1
while number 1000:
lastTime = currentTime
oneSecondMarkReached = True

Нам нужно было немного изменить код, чтобы мигание
происходило каждую секунду и звуковой сигнал — каждую секунду.
Мы используем флаг oneSecond-MarkReached и устанавливаем его
каждые 1000 тиков (1 секунда по времени). Затем он сбрасывается
после того, как подаст звуковой сигнал и изменит цвет на текст
«Hello World».

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

Крестики-нолики в PyGame

Вступление
Добро пожаловать в главу о написании игры Tic Tac Toe с
помощью PyGame. В этой главе мы рассмотрим основы
библиотеки PyGame и способы ее использования для написания
простой игры Tic Tac Toe для двух игроков. Мы расскажем, как
нарисовать игровое поле, как определить ввод пользователя и
как реализовать базовый ИИ для игры. К концу этой главы у вас
должна быть работающая игра «Крестики-нолики», в которую вы
сможете играть против компьютера. Итак, приступим!

40

Крестики-нолики в PyGame

Основной цикл
Следующий код представляет собой основной игровой цикл для
игры в крестики-нолики, реализованной с помощью библиотеки
PyGame. Он запускает цикл обработки событий для проверки
пользовательского ввода, а затем рисует игровое поле. Если игра
не окончена, он проверяет, поставил ли игрок X, затем ждет
полсекунды, чтобы имитировать мышление ИИ, прежде чем
поставить O. После того, как он поставит O, он проверяет, выиграл
ли кто-нибудь игру, и если никто не выиграл, он проверяет,
является ли это ничьей. Наконец, он обновляет дисплей.

1
2

####################################################

3

# Основной игровой цикл

4

####################################################
while True:
if game_over:
pygame.display.flip()
pygame.time.delay(1000)
draw_game_over_screen()

5
6
7
8
9
10
11
12

# Запустите обработку события, чтобы проверить выход
check_for_quit_event()
else:

13

game_window.fill(WHITE) # проверка белого фона

14

# для выхода и нажатия мыши
run_event_processing()

15
17

# Проверить на победу или ничью
game_over = check_for_win_or_draw()

18

draw_the_board()

19

pygame.display.flip()

16

# Нарисуйте игровое поле
# обновить экран

20

# Проверьте, выиграл ли кто-нибудь после того,
# как X был размещен

Крестики-нолики в PyGame

21
22

41

if game_over:
continue

23
24
25

# ИИ идет сюда, чтобы поставить O
if X_placed:

26

# Подождите 1/2 секунды, чтобы это выглядело

27

30

# так, будто ИИ думает
pygame.time.delay(500)
O_placed = run_algorithm_to_place_O()
game_over = check_if_anyone_won()

31

# Снова нарисуйте доску, чтобы показать букву О,

32

# которую мы только что поставили.
draw_the_board()
X_placed = False

28
29

33
34
35
36
37

# обновить экран
pygame.display.flip()

38
39
40

# ограничить цикл до 60 кадров в секунду
clock.tick(60)

Обработка событий
В основе содержимого игрового цикла лежит несколько функций,
которые используют pygame для выполнения тяжелой работы.
Давайте сначала посмотрим на функцию DoEventProcessing. Этот код
представляет собой функцию в PyGame, которая запускает цикл
обработки событий для проверки пользовательского ввода и
щелчков мышью. Когда пользователь нажимает на доску, он
обрабатывает событие нажатия мыши для X и устанавливает флаг
X_placed в значение True, а также проверяет, решил ли пользователь
выйти из игры. Событие выхода запускается, когда пользователь
закрывает окно.

42

Крестики-нолики в PyGame

1
2
3

def run_event_processing():
global X_placed
global game_over

4
5
6
7
8
9

for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()

# выйти из игры

quit()
if event.type == pygame.MOUSEBUTTONDOWN:

10

# Разместить X на доске

11

handle_mouse_down_for_x()
X_placed = True

12

Теперь давайте посмотрим на вызываемую функцию
handle_mouse_down_for_x. Этот код используется для обработки
события нажатия мыши для размещения X на доске для игры в
крестики-нолики. Он использует функцию mouse.get_pos() библиотеки
PyGame для получения положения мыши, затем делит строку и
столбец на ширину и высоту сетки, чтобы получить строку и
столбец клика. Наконец, он устанавливает соответствующую
позицию в массиве доски в «X».

1
2
3
4
5

def handle_mouse_down_for_x():
(row, col) = pygame.mouse.get_pos()
row = int(row / grid_width)
col = int(col / grid_height)
board[row][col] = "X"

Создание доски
Функция draw_the_board используется для рисования доски Tic Tac
Toe (крестики и нолики) в ее текущем состоянии. Он перебирает
все строки и столбцы доски и вызывает функцию
draw_game_board_square() для рисования каждого квадрата. Затем
он проверяет, содержит ли доска в этой строке и столбце «X» или
«O», и вызывает функцию draw_tic_tac_toe_letter() для рисования
соответствующей буквы.

Крестики-нолики в PyGame

1
2
3
4

def draw_the_board():
for row in range(grid_size):
for col in range(grid_size):
draw_game_board_square(row, col)

5

# Сделать букву X

6

if (board[row][col] == "X"):
draw_tic_tac_toe_letter(row, col, 'X')

7
8
9
10

# Сделать букву O
if (board[row][col] == "O"):
draw_tic_tac_toe_letter(row, col, 'O')

Создание игрового поля
Этот код используется для рисования квадрата игрового поля в
указанной строке и столбце. Он использует функцию Rect()
библиотеки PyGame для создания прямоугольного объекта с
заданными строкой, столбцом, шириной и высотой. Затем он
использует функцию draw.rect() для рисования прямоугольника в
окне игры черным цветом и шириной линии 3.
1
2
3
4
5
6

def draw_game_board_square(row, col):
rect = pygame.Rect(col * grid_width, row *
grid_height,
grid_width,
grid_height)
pygame.draw.rect(game_window, BLACK, rect, 3)

Рисуем крестики-нолики
Этот код используется для рисования буквы «X» или «O» в
указанной строке и столбце. Он использует функцию font.render()
из библиотеки PyGame для рендеринга буквы как объекта Surface и
задает черный цвет. Затем он использует метод game_window.blit()
для рисования буквы в указанной строке и столбце, причем
строка и столбец умножаются на ширину и высоту сетки, плюс
четверть ширины и высоты сетки для ее центрирования.

43

Крестики-нолики в PyGame

1
2
3
4
5

44

def draw_tic_tac_toe_letter(row, col, letter):
letter_piece = font.render(letter, True, BLACK)
game_window.blit(
letter_piece, (row * grid_width + grid_width/4,
col * grid_height + grid_height/4))

«AI» для размещения O
Для упрощения алгоритм размещения буквы O заключается в том,
чтобы просто найти следующую доступную клетку. Мы улучшим это
позже в этой главе, но эта стратегия должна, по крайней мере,
позволить вам играть в игру против компьютерного оппонента.
AI - искусственный интелект (ИИ)
1

####################################################

2

# Очень простой алгоритм размещения O на доске.

3

# Пройдитесь по всей доске и найдите первую доступную

4

# клетку. Поместите О там.

5

####################################################
def run_algorithm_to_place_O():
for rowo in range(grid_size):
for colo in range(grid_size):
if (board[rowo][colo] == 0):
board[rowo][colo] = "O"
return True

6
7
8
9
10
11
12
13

return False

Крестики-нолики в PyGame

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

1
2

def check_if_anyone_won():
global winner

3

#Проверить, выиграл ли кто-то по горизонтали

4

8

for row in range(3):
if board[row][0] == board[row][1]
== board[row][2] != 0:
winner = board[row][0]
return True

9

#Проверить, выиграл ли кто-то по вертикали

5
6
7

14

for col in range(3):
if board[0][col] == board[1][col]
== board[2][col] != 0:
winner = board[0][col]
return True

15

#Проверить, выиграл ли кто-то по диагонали

16

if board[0][0] == board[1][1]
== board[2][2] != 0:
winner = board[0][0]
return True
if board[0][2] == board[1][1]
== board[2][0] != 0:
winner = board[0][2]
return True

10
11
12
13

17
18
19
20
21
22
23
24
25

#никто не выиграл, вернуть false

26

return False

45

Крестики-нолики в PyGame

46

Проверка на ничью
Нам также нужно проверить, не осталось ли мест для размещения
X или O, и ни один из игроков не выиграл игру. Для этого мы
создадим новую функцию, которая проверяет, заполнена ли доска.
Если никто не выиграл и доска заполнена, то ничья.

1
2
3
4
5
6

def check_if_board_is_full():
for row in range(3):
for col in range(3):
if board[row][col] == 0:
return False
return True

7
8
9

####################################################

10

#Проверьте, есть ли ничья, проверив, заполнена ли доска

11

#и никто не выиграл

12

####################################################

13
14
15
16

def check_if_draw():
return not (check_if_anyone_won()) and
check_if_board_is_full()

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

47

Крестики-нолики в PyGame

1

if game_over:

2

#нарисовать игру на экране

3
5

pygame.display.flip()
pygame.time.delay(1000)
draw_game_over_screen()

6

check_for_quit_event()

4

# чтобы проверить выход

7
8
9

# Запустите обработку события,

else:
#нарисовать крестики / нолики на доске

Следующий код Python рисует игру на экране вместо доски для игры
в крестики-нолики, как только мы определили, что игра завершена.
Он проверяет строку победителя и отображает соответствующее
сообщение о том, что произошло в игре, на основе этой строки.
Экран Game Over также дает игроку возможность выбора: играть в
новую игру или нет:

1

####################################################

2

# Нарисуйте игру на экране, показывая, кто выиграл

3

####################################################
def draw_game_over_screen():
game_window.fill(WHITE)
if winner == "X":
text = font.render('X Wins!', True, BLACK)
elif winner == "O":
text = font.render('O Wins!', True, BLACK)
else:
text = font.render('Draw!', True, BLACK)

4
5
6
7
8
9
10
11
12
13
14

playAgainText = smallfont.render(
'Play Again (y/n)?', True, BLACK)

15
16
17
18

game_window.blit(text,
(window_width/2 - 200, window_height/2 - 100))

Крестики-нолики в PyGame

19
20

game_window.blit(playAgainText,
(window_width/2 - 200, window_height/2 + 50))

Получившееся изображение экрана игра закончена показано ниже:

Играю снова
Чтобы позволить игроку играть в новую игру, нам нужно очистить
состояние текущей игры, когда пользователь нажимает клавишу y.
Мы сбрасываем глобальное состояние игры с помощью новой
функции с именем initialize_game_values. Этот метод срабатывает,
если пользователь нажимает клавишу «y» в состоянии окончания
игры:

48

Крестики-нолики в PyGame

1
2
3
4
5
6
7
8
9
10
11
12
13

49

def check_for_quit_event():
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_y:
initialize_game_values()
game_window.fill(WHITE)
return True
elif event.key == pygame.K_n:
pygame.quit()
quit()

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

1
2
3
4
5
6
7

def initialize_game_values():
global board
global game_over
global X_placed
global O_placed
global winner
global clock

8
9
10
11
12

game_over = False
X_placed = False
O_placed = False
winner = ''

13
14

board = [

Крестики-нолики в PyGame

[0, 0, 0],
[0, 0, 0],
[0, 0, 0],

15
16
17

]

18
19

clock = pygame.time.Clock()

20

Лучший ИИ
Помните нашу дискуссию о внедрении более продвинутого ИИ
(искусственного интеллекта) для игрока «О» в игре? Этот
улучшенный алгоритм разработан, чтобы никогда не проигрывать!
Вот шаги для этого уточненного алгоритма:
1.
2.

3.

4.
5.
6.

7.

Подсчитайте количество сделанных ходов.
Если это второй ход (был сделан только один ход): a.
Поместите «О» в центр доски, если она пуста. б. Если
центр занят, поставьте «О» в первый доступный угол.
Для всех пустых позиций на доске: a. Проверьте, приведет
ли размещение «O» в текущей позиции к победе для «O»
игрока. Если это так, поставьте «O» и верните True. б.
Проверьте, не помешает ли размещение «O» в текущей
позиции выиграть игроку «X». Если это так, поставьте «O»
и верните True.
Если «O» начинается в углу, поместите «O» в первый
доступный угол и верните True.
Поместите «O» в первую доступную неугловую боковую
позицию и верните True.ы
Если ни одно из вышеперечисленных условий не
применимо, поместите «O» в первую доступную позицию и
верните True.
Если свободных позиций нет, вернуть False.

Алгоритм использует ряд правил для определения оптимальной
позиции для размещения «О» на доске «Крестики-нолики». Он
учитывает текущее состояние доски и принимает решения на основе
победы или блокировки соперника от победы, а также
расставляет приоритеты угловых и неугловых сторон в зависимости
от ситуации.

50

Крестики-нолики в PyGame

Вот код Python. Обратите внимание, что мы разбили его на 3
функции run_better_algorithm_to_place_O, is_winning_move и
get_empty_positons:

1

# check if placing a piece in the row

2

# and column results in a winning move

3

def is_winning_move(player, row, col):
n = len(board)

4
5

# Проверить ряд

6
8

if all(board[row][j] == player
for j in range(n)):
return True

9

# Провер. столбец

7

12

if all(board[i][col] == player
for i in range(n)):
return True

13

# Проверить главную диагональ

14
16

if row == col and all(board[i][i]
== player for i in range(n)):
return True

17

#Проверить второстепенную диагон.

18

if row + col == n - 1 and
all(board[i][n - i - 1]
== player for i in range(n)):
return True
return False

10
11

15

19
20
21
22
23
24

#вернуть пустые позиции на доске в списке

25

def get_empty_positions():
empty_positions = []
for i, row in enumerate(board):
for j, cell in enumerate(row):
if cell == 0:

26
27
28
29

51

Крестики-нолики в PyGame

30
31

empty_positions.append((i, j))
return empty_positions

32
33
34
35
36
37
38

def run_better_algorithm_to_place_O():
grid_size = len(board)
empty_positions = get_empty_positions()
num_moves = sum(1 for row in board for
cell in row if cell != 0)

39
40

#Второй ход: поместите букву «О» в центр или угол.

41

if num_moves == 1:
center = grid_size // 2
if board[center][center] == 0:
board[center][center] = "O"
return True
else:
for row, col in
[(0, 0), (0, grid_size - 1),
(grid_size - 1, 0),
(grid_size - 1, grid_size - 1)]:
if board[row][col] == 0:
board[row][col] = "O"
return True

42
43
44
45
46
47
48
49
50
51
52
53
54
55

# Попробуйте выиграть или заблокируйте X для победы

56

for row, col in empty_positions:

57

#Проверьте, выиграет ли игра от размещения "O"

58

board[row][col] = "O"
if is_winning_move("O", row, col):
return True
board[row][col] = 0

59
60
61
62
63

# Проверьте, не заблокирует ли Х размещению «О»

64

for row, col in empty_positions:

52

Крестики-нолики в PyGame

65
66
67
68
69

board[row][col] = "X"
if is_winning_move("X", row, col):
board[row][col] = "O"
return True
board[row][col] = 0

70
71

#Поместите «О» в угол в начале игры

72

if board[0][0] == "O"
or board[0][grid_size - 1] == "O"
or board[grid_size - 1][0] == "O"
or board[grid_size - 1][grid_size - 1]
== "O":
for row, col in
[(0, 0), (0, grid_size - 1),
(grid_size - 1, 0),
(grid_size - 1, grid_size - 1)]:
if board[row][col] == 0:
board[row][col] = "O"
return True

73
74
75
76
77
78
79
80
81
82
83
84
85

#Поместите «О» на неугловой стороне

86

for row, col in empty_positions:
if row not in [0, grid_size - 1]
and col not in [0, grid_size - 1]:
board[row][col] = "O"
return True

87
88
89
90
91
92

# Поместите «О» в любое доступное место

93

for row, col in empty_positions:
board[row][col] = "O"
return True

94
95
96
97

return False

53

Крестики-нолики в PyGame

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

54

https://t.me/it_boooks/2

Использование классов
в Pygame
Введение
Крестики и нолики и другие игры, которые мы пишем в Pygame,
способствуют тому, чтобы их можно было разбить на более мелкие
объекты, с которыми может работать программа. Классы в Python
могут помочь разработчикам создавать повторно используемый и
поддерживаемый код. Благодаря организации кода в классы его
легче читать и понимать, а также можно более эффективно
выполнять отладку и тестирование. Кроме того, это позволяет
разработчикам легко изменять и добавлять в игру новые элементы
без необходимости переписывать код, что особенно полезно при
разработке сложных игр. Классы также помогают поддерживать
порядок в коде и упрощают реализацию новых функций. Наконец,
использование классов помогает разработчикам создавать более
эффективный код, поскольку они могут легко использовать один и
тот же код для похожих частей игры.
Давайте погрузимся в создание классов для нашей игры. Класс
Python, представляющий букву (X или O), которую можно
нарисовать на экране в pygame, можно определить следующим
образом:
Вот пример класса Pygame, который наследуется от класса Sprite и
рисует на экране «X» или «O»:

Использование классов в Pygame

1

56

import pygame

2
3
4

class LetterSprite(pygame.sprite.Sprite):

5
6
7
8
9
10
11
12
13
14
15
16

def __init__(self, letter, row, column,
grid_width, grid_height):
#инициализировать базовый класс спрайта
super().__init__()
font = pygame.font.Font(None, 150)
# визуализировать шрифт на поверхности изображения
self.image = font.render(letter, True, (0, 0, 0))
# определить границы изображения на доске
self.rect = self.image.get_rect().move(
row * grid_width + grid_width / 3,
column * grid_height + grid_height / 3)

17
18
19

def update(self):
pass

20
21
22
23

def draw(self, surface):
letter_piece = self.image
surface.blit(letter_piece, self.rect)

Класс Letter принимает 5 аргументов: сама буква, которая хранится
как переменная экземпляра, строка и столбец, в которых буква
размещается на доске, и размеры сетки. Конструктор
инициализации выполняет большую часть тяжелой работы. Он
создает изображение из шрифта по умолчанию и вычисляет
положение прямоугольника из размеров строки, столбца и сетки.
Так как движения от текущего положения нет, письмо не нуждается
в методе обновления, поэтому с обновлением мы ничего не
делаем. Метод draw() принимает только один аргумент: экран, то
есть поверхность игры. Затем он использует pygame.Surface как
средство для вывода письма на экран игры.
Мы можем создать наш LetterSprite, как только игрок нажмет
событие mousedown, а затем мы можем использовать класс Group в
pygame, чтобы собрать все x, которые мы добавляем на доску.

Использование классов в Pygame

1
2
3
4
5
6
7
8

57

def handle_mouse_down_for_x():
(row, col) = pygame.mouse.get_pos()
row = int(row / grid_width)
col = int(col / grid_height)
board[row][col] = "X"
letterX = LetterSprite('X', row, col,
grid_width, grid_height)
group.add(letterX)

Причина, по которой мы добавляем X в группу, заключается в том,
что когда мы хотим отрисовать все игровые фигуры, мы просто
вызываем group.draw(surface), и она нарисует для нас все игровые
фигуры сразу. Как мы скоро увидим, то же самое можно сделать и с
буквой «О»!
Теперь мы можем удалить 90% кода, рисующего X и O, и он сведется
к одной строке кода: group.draw(game_window)
1

def draw_the_board():

2
3

group.draw(game_window)

4
5
6
7

for row in range(grid_size):
for col in range(grid_size):
draw_game_board_square(row, col)

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

Использование классов в Pygame

1

58

import pygame

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# Создать спрайт
class GameBoardSquareSprite(pygame.sprite.Sprite):
def __init__(self, color, row, column, width, height):
super().__init__()
self.width = width
self.height = height
# Create a surface for the sprite
self.image = pygame.Surface([width, height])
#сделать фоновую игровую плитку белой
self.image.fill((255, 255, 255))
self.rect = self.image.get_rect().move(row*width,
column*height)
#Нарисуйте прямоугольник на поверхности спрайта
pygame.draw.rect(self.image, color, pygame.Rect(
0, 0, width, height), 2)

18
19

# Рисуем спрайт на экране

20
21
22

def draw(self, surface):
surface.blit(self.image, 0, 0)

Теперь в нашей initialize_game_board мы добавим игровые плитки в
группу:
1
2
3
4
5
6
7

def initialize_game_board():
for row in range(3):
for column in range(3):
game_board_square = GameBoardSquareSprite(
(0, 255, 0),
grid_width, grid_height)
group.add(game_board_square)

Когда вызывается group.draw, он отрисовывает тайлы, а также
сыгранные крестики и нолики. Наша функция draw_the_board теперь
выглядит так:

Использование классов в Pygame

1
2

def draw_the_board():
group.draw(game_window)

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

59

Рефакторинг
игровой логики
Чтобы еще больше разбить код на модули, мы можем вынести
всю игровую логику из основного модуля Python в класс под
названием GameBoard, который будет проверять выигрыши,
проигрыши и ничьи, а также даст нам возможность заполнить
доску нашими предположениями.
Он также может управлять алгоритмической логикой размещения
букв «О».

1
2
3
4
5

class GameBoard:
def __init__(self, grid_size):
self.grid_size = grid_size
self.winner = ''
self.initialize_board()

6
7

####################################################

8

#Инициализировать доску нулями

9

####################################################

10
11
12
13
14
15
16

def initialize_board(self):
self.board = [
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
]

17
18

############################################################

19

Проверьте, выиграл ли кто-то в любой строке, столбце или диагонали

20

#############################################################

21

Рефакторинг игровой логики

22
23

def check_if_anybody_won(self):
# Проверить, выиграл ли кто-то по горизонтали

24
25
26
27
28
29

for row in range(3):
if self.board[row][0] == self.board[row][1]
== self.board[row][2] != 0:
self.winner = self.board[row][0]
return True

30
31

#Проверить, выиграл ли кто-то по вертикали

32

for col in range(3):
if self.board[0][col] == self.board[1][col]
== self.board[2][col] != 0:
self.winner = self.board[0][col]
return True

33
34
35
36
37
38

# Проверить, выиграл ли кто-то по диагонали

39

if self.board[0][0] == self.board[1][1]
== self.board[2][2] != 0:
self.winner = self.board[0][0]
return True
if self.board[0][2] == self.board[1][1]
== self.board[2][0] != 0:
self.winner = self.board[0][2]
return True

40
41
42
43
44
45
46
47
48

return False

49
50

####################################################

51

# Проверьте, заполнена ли доска

52

####################################################

53
54
55
56

def check_if_board_is_full(self):
for row in range(3):
for col in range(3):

61

Рефакторинг игровой логики

57
58
59

if self.board[row][col] == 0:
return False
return True

60
61

####################################################

62

# Проверьте, есть ли ничья, проверив, заполнена

63

# ли доска и никто не выиграл.

64

####################################################

65
66
67
68

def check_if_draw(self):
return not (self.check_if_anybody_won()) and
self.check_if_board_is_full()

69
70

####################################################

71

# Поместите X

72

####################################################
def place_X(self, row, col):
self.board[row][col] = "X"

73
74
75
76

#################################################

77

# Используется run_better_algorithm_to_place_O, чтобы

78

# определить, приводит ли размещение фигуры в строке

79

# или столбце на доске к выигрышному ходу. Это

80

# используется для определения блокировки,а также

81

# для победы противника "O".

82

###############################################
def is_winning_move(self, player, row, col):
n = len(self.board)
# Check row
if all(self.board[row][j] == player
for j in range(n)):
return True
# Check column
if all(self.board[i][col] == player
for i in range(n)):

83
84
85
86
87
88
89
90
91

62

Рефакторинг игровой логики

92
93
94
95
96
97
98
99
100
101
102

return True
# Check main diagonal
if row == col and all(self.board[i][i] ==
player for i in range(n)):
return True
# Check secondary diagonal
if row + col == n - 1 and
all(self.board[i][n - i - 1]
== player for i in range(n)):
return True
return False

103
104
105
106
107
108
109
110
111
112
113
114

#############################################
# Используется методом run_better_algorithm_to_place_O
# для сбора всех доступных позиций на доске.
##############################################
def get_empty_positions(self):
empty_positions = []
for i, row in enumerate(self.board):
for j, cell in enumerate(row):
if cell == 0:
empty_positions.append((i, j))
return empty_positions

115
117

#####################################################
# Использует алгоритм, чтобы решить, где разместить O.

118

# Этот алгоритм никогда не проигрывает.

119

#####################################################
def run_better_algorithm_to_place_O(self):
grid_size = len(self.board)
empty_positions = self.get_empty_positions()
num_moves = sum(1 for row in self.board for
cell in row if cell != 0)

116

120
121
122
123
124
125
126

# Второй ход: поместите «О» в центр или угол.

63

Рефакторинг игровой логики

127
128
129
130
131
132
133
134
135
136
137
138
139

if num_moves == 1:
center = grid_size // 2
if self.board[center][center] == 0:
self.board[center][center] = "O"
return (True, center, center)
else:
for row, col in [(0, 0),
(0, grid_size - 1),
(grid_size - 1, 0),
(grid_size - 1, grid_size - 1)]:
if self.board[row][col] == 0:
self.board[row][col] = "O"
return (True, row, col)

140
141

# Попробуйте выиграть или заблокируйте X от победы

142

for row, col in empty_positions:
# Проверьте, выиграет ли игра от размещения "O"

143
144
145
146
147

self.board[row][col] = "O"
if self.is_winning_move("O", row, col):
return (True, row, col)
self.board[row][col] = 0

148
149

# Проверьте, не помешает ли размещение «О» X выиграть

150

for row, col in empty_positions:
self.board[row][col] = "X"
if self.is_winning_move("X", row, col):
self.board[row][col] = "O"
return (True, row, col)
self.board[row][col] = 0

151
152
153
154
155
156
157

# Поместите «О» в угол в начале игры

158

if self.board[0][0] == "O"
or self.board[0][grid_size - 1] == "O"
or self.board[grid_size - 1][0] == "O"
or self.board[grid_size - 1][grid_size - 1]

159
160
161

64

Рефакторинг игровой логики

162
163
164
165
166
167
168
169

== "O":
for row, col in [(0, 0), (0, grid_size - 1),
(grid_size - 1, 0),
(grid_size - 1, grid_size - 1)]:
if self.board[row][col] == 0:
self.board[row][col] = "O"
(True, row, col)
return (True, row, col)

170
171

# Поместите «О» на неугловой стороне

172

for row, col in empty_positions:
if row not in [0, grid_size - 1]
and col not in [0, grid_size - 1]:
self.board[row][col] = "O"
return (True, row, col)

173
174
175
176
177
178

#Поместите «О» в любое доступное место

179

for row, col in empty_positions:
self.board[row][col] = "O"
return (True, row, col)

180
181
182
183

return (False, -1, -1)

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

1

board = GameBoard(grid_size)

Затем везде, где мы используем доску, мы просто вызываем ее
методы.
Ниже приведен вызов метода run_better_algorithm_to_place_O
GameBoard, куда мы помещаем спрайт O из основной игровой
программы. Алгоритмический метод на доске возвращает кортеж,
указывающий, смогли ли мы найти место для фигуры на доске, и
если да, то в какой строке и столбце она была размещена.

65

Рефакторинг игровой логики

1
2
3
4
5
6
7
8

66

(O_placed, rowo, colo) =
board.run_better_algorithm_to_place_O()
if O_placed:
letterO = LetterSprite(
'O', colo, rowo,
grid_width,
grid_height)
group.add(letterO)

Мы также можем получить доступ к любым внутренним атрибутам
класса GameBoard, например к победителю игры:

1
2
3
4
5
6

if board.winner == "X":
text = font.render('X Wins!', True, BLACK)
elif board.winner == "O":
text = font.render('O Wins!', True, BLACK)
else:
text = font.render('Draw!', True, BLACK)

Глядя на этот код, на самом деле это возможность реорганизовать
метод, который возвращает строку победителя для экрана Game
Over. Итак, мы добавим в GameBoard новый метод
get_winner_display_message:

Рефакторинг игровой логики

1
2
3
4
5
6
7

67

def get_winner_display_message(self):
if self.winner == 'X':
return 'X Wins!'
elif self.winner == 'O':
return 'O Wins!'
else:
return 'Draw!'

а затем вызовите его из функции draw_game_over_screen в нашей
основной программе pygame.

1
2
3

def draw_game_over_screen():
game_window.fill(WHITE)
winnerMessage = board.get_winner_display_message()

4
5

text = font.render(winnerMessage, True, BLACK)

6
7

# получить ширину текста, чтобы мы могли

8

# центрировать по горизонтали
text_width = text.get_width()

9
10
11
12

playAgainText = smallfont.render('Play Again (y/n)?',
True, BLACK)
'Играть снова'

13
14

# получить подсказку о ширине размещения, чтобы

15

# мы могли центрировать еепо горизонтали
playAgainText_width = playAgainText.get_width()

16
17
18
19
20

game_window.blit(
text, (window_width/2 - text_width/2,
window_height/2 - 100))

21
22
23
24

game_window.blit(playAgainText,
(window_width/2 - playAgainText_width/2,
window_height/2 + 50))

Рефакторинг игровой логики

Этот рефакторинг кода взял на себя ответственность за то, чтобы
знать, как был определен победитель из основной программы, и
поместил его в черный ящик, который мы могли вызывать из
нашего объекта board.

Заключение
В этой главе мы рассмотрели использование классов в pygame для
улучшения организации и удобства сопровождения вашего кода.
Мы достигли этого, используя класс GameBoard для обработки
игровой логики и управления данными, возложив ответственность
за отрисовку на классы спрайтов. Благодаря своим методам и
свойствам эти классы были интегрированы в основной цикл.

68

Рефакторинг игровой логики

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

69

https://t.me/it_boooks/2

Глава 6 - Пожиратель камней

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

Глава 6 - Пожиратель камней

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

Проект игры
Классы
Мы воспользуемся классами для нашей игры. В игре мы будем
использовать несколько классов спрайтов, каждый из которых
представляет игровой объект. Будет спрайт, используемый для
рисования пожирателя камней, а также спрайт, используемый для
рисования всех драгоценных камней в игре. Также у нас будут
спрайты для каждой статистики, которую мы используем в игре:
счет, высокий счет, координаты и время. Мы также создадим спрайт
общего сообщения для публикации текста, например «играть снова?
» Кроме того, как мы это делали в крестиках-ноликах, мы создадим
класс игровой доски, который будет контролировать всю игровую
логику и состояние игры, когда игрок ест камень. Ниже приведен
список классов, которые мы только что упомянули:

PlayerSprite StoneSprite GameBoard ScoreSprite
HiScoreSprite TimeSprite CoordinateSprite MessageSprite

Макет игры
Как и в крестики-нолики, игра с пожирателем камней представляет
собой сетку. Игровое поле в этой игре представляет собой сетку
20x20, каждая ячейка которой имеет ширину 20 пикселей. Когда игрок
перемещается вверх, вниз, влево или вправо, пожиратель камней
перемещается на следующую соседнюю ячейку в сетке.
Драгоценные камни также случайным образом размещаются внутри
сетки в разных ячейках.
Хотя игровое поле представляет собой сетку 20 x 20, мы используем
только нижние 17 рядов, чтобы оставить место для спрайтов в
верхней части игры.

71

Глава 6 - Пожиратель камней

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

Инициализация игры
Прежде чем мы начнем игровой цикл, как и в любой другой игре,
нам нужно инициализировать игровые элементы. Функция
initialize_game_state устанавливает начальное состояние игры Stone
Eater. Эта функция начинается с объявления нескольких
глобальных переменных, которые будут использоваться на
протяжении всей игры: gems_collected, gems_score, start_time, times_up и
already_played.

72

Глава 6 - Пожиратель камней

73

Затем функция очищает группу gem_group, чтобы удалить все
существующие камни на игровом поле. Она устанавливает start_time
равным текущему времени и устанавливает times_up равным False.
Начальная позиция игрока устанавливается в 3-й строке и первом
столбце игрового поля. Время игры и счет обновляются до своих
начальных значений time_limit и 0 соответственно. Для переменных
gems_collected и gems_score установлено значение 0, а для
уже_играемого установлено значение False. Наконец, функция
вызывает метод initialize_board для сброса логики игрового поля в
исходное состояние.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

def initialize_game_state():
global gems_collected, gems_score, start_time,
times_up, already_played
print("Initializing game state")
gem_group.empty()
start_time = pygame.time.get_ticks()
times_up = False
player.row = player_limit
player.column = 0
player.update()
game_time.update_time(time_limit, 0)
score.update_score(0)
gems_collected = 0
gems_score = 0
already_played = False

16
17

game_board.initialize_board()

18
19
20

initialize_game_state()

Цикл игры
Как и в крестиках-ноликах, игра состоит из приема событий и
рисования спрайтов в зависимости от состояния доски и событий.

Глава 6 - Пожиратель камней

74

Ниже приведена грубая архитектура игрового цикла, который
управляет всей игрой Stone Eater.
1

while running:
# Получить событие клавиатуры для перемещения

2
игрока
3

# обновить позицию спрайта игрока, если была нажата

клавиша

4

#определить, столкнулся ли игрок с камнем

5

# если игрок столкнулся,

6

# удалить камень и воспроизвести звук

7
8

#
# обновить спрайты подсчета очков

9

# нарисовать все драгоценные камни,

10

# если время вышло

11

#

12

#

обновить высокий балл

13

#

включи музыку в конце игры

14

# еще

15

# рисовать спрайты игрока и игрового времени

16

# нарисовать все спрайты драгоценных камней

17

# нарисовать все спрайты очков,

18

# обратный цикл

ничья воспроизвести снова (д/н) сообщение

Теперь давайте посмотрим на реальный игровой цикл. Ниже
приведен полный код игрового цикла «Пожиратель камней»,
который, как вы заметите, похож на игровой цикл
«крестики-нолики». Как мы описали в нашем псевдокоде, цикл
обрабатывает события от пользователя, соответственно обновляет
игровое поле и рисует все на игровом поле. Он использует группы
спрайтов для выполнения как обновления, так и отрисовки спрайтов
каждый раз в цикле. Игровой цикл также воспроизводит звуки, когда
это необходимо: мы проигрываем звук каждый раз, когда съедаем
камень, и мы проигрываем музыку, когда время истекает и игра
завершена.

75

Глава 6 - Пожиратель камней

1
2
3
4
5

# Основной
игровой цикл
running = True
while running:
running = process_events()

# обработать клавиатуру

6
7

# Проверить, подобрал ли игрок драгоценный камень

8

if game_board.check_for_gem(player) and
(times_up == False):

9
10

# драгоценный камень найден, обновите счет

11

gems_collected += 1
gems_score += game_board.get_cell_value(
player.row, player.column)
score.update_score(gems_score)
# убрать драгоценный камень с доски и спрайт драгоценного камня
game_board.remove_gem(player)
which_sprite = detect_collisions(player,
gem_group, piece_size)
remove_sprite_from_group(which_sprite, gem_group)
got_coin_sound.play()

12
13
14
15
16
17
18
19
20
21
22
23
24

# Обновить координаты
coordinates.update_coordinatees(player.row, player.co\
lumn)

25
26

# Время обновления

27

game_time.update_time(time_limit,
pygame.time.get_ticks() - start_time)

28
29
30

# Проверить, истекло ли время

31

if (pygame.time.get_ticks() start_time > time_limit * 1000)
and (times_up == False):
times_up = True

32
33
34
35

Глава 6 - Пожиратель камней

36
37
38

# очистить экран
window.fill(WHITE)

39

# Проверяем, установлен ли флаг таймаута

40

if times_up:

41

# установить текущий счет для сравнения

42

# с высоким баллом

43

# и один раз включи музыку в конце игры

44

if already_played == False:
hi_score.current_score = gems_score
victory_sound.play()
already_played = True

45
46
47

76

48
49
50

gems_collected_message.update_message(
f'You collected {str(gems_collected)} gems!')

51
52
53
54
55

gems_collected_message.update()
gems_collected_message.draw(window)
play_again_message.draw(window)
else:

56

# розыгрыш игрока и игрового времени

57

player.draw(window)
game_time.draw(window)

58
59
60

# нарисуй драгоценные камни

61

gem_group.draw(window)

62
63

# обновить статистику

64

# (счет, его счет, координаты, оставшееся время)

65

score_group.update()

66
67

# нарисуй статистику

68

score_group.draw(window)

69
70

# отображать всю графику

Глава 6-Каменная Вода

71
72

77

# мы только что нарисовали
pygame.display.flip()

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

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

Глава 6 - Пожиратель камней

6

for event in pygame.event.get():
if event.type == pygame.QUIT or
(event.type == pygame.KEYDOWN and
event.key == pygame.K_n and times_up == True):
running = False
elif event.type == pygame.KEYDOWN:

7

# Проверить, переместился ли игрок

8

if event.key == pygame.K_UP
and player.row > player_limit:
player.row -= 1
player.update()
elif event.key == pygame.K_DOWN
and player.row < GRID_LENGTH - 1:
player.row += 1
player.update()
elif event.key == pygame.K_LEFT and
player.column > 0:
player.column -= 1
player.update()
elif event.key == pygame.K_RIGHT and
player.column < GRID_LENGTH - 1:
player.column += 1
player.update()
elif event.key == pygame.K_y and
times_up == True:
initialize_game_state()

1
2
3
4
5

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

78

Игровое поле
Как и в крестиках-ноликах, игровое поле в Stone Eater используется
для размещения драгоценных камней, а также для отслеживания
местоположения драгоценных камней и определения, были ли они
съедены или нет. Ниже приведены методы класса Game Board и их
назначение.
init (constructor) initialize_board -размещает начальные камни на
доске

Глава 6 - Пожиратель камней

check-for-gem - проверяет, есть ли в строке и столбце
драгоценный камень
remove_gem - удаляет драгоценный камень с доски
get_cell_value - получить значение ячейки в указанной строке и
столбце
Когда мы инициализируем доску, мы размещаем драгоценные
камни в случайных незанятых местах на доске.
1
2
3

def initialize_board(self):
# заполнить пустую сетку матрицей 20 x 20 из 0

8

self.grid = []
for i in range(self.grid_size):
self.grid.append([])
for j in range(self.grid_size):
self.grid[i].append(0)

9

# Размещайте драгоценные камни случайным образом на self.grid

4
5
6
7

22

num_gems = 20
for i in range(num_gems):
gem_placed = False
while not gem_placed:
row = random.randint(self.player_limit,
self.grid_size - 1)
column = random.randint(0,
self.grid_size - 1)
if self.grid[row][column] == 0:
self.grid[row][column]
= random.randint(1, 3)
gem_placed = True
#добавляем каменные спрайты в группу драгоценных камней

23

# и размещаем их на доске

24

self.gem_group.add(StoneSprite(
self.colors[
self.grid[row][column]-1],
row, column, self.piece_size,
self.grid[row][column]))

10
11
12
13
14
15
16
17
18
19
20
21

25
26
27
28

79

Глава 6 - Пожиратель камней

Below is the entire class that includes all the methods described
above. The GameBoard class makes it easier to do all the game logic
as it relates to the board and hides the internal grid mapping from
the game loop so the game loop doesn’t have to think about it.

8

class GameBoard:
def __init__(self, size, piece_size,
player_limit, gem_group):
self.grid_size = size
self.piece_size = piece_size
self.player_limit = player_limit
self.grid = []
self.gem_group = gem_group

9

# цвета драгоценных камней

1
2
3
4
5
6
7

10
11
12
13

GREEN = (0, 150, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
self.colors = [GREEN, RED, BLUE]

14
15

def initialize_board(self):

16

# заполняем пустую сетку матрицей 20 x 20 из нулей

17

self.grid = []
for i in range(self.grid_size):
self.grid.append([])
for j in range(self.grid_size):
self.grid[i].append(0)

18
19
20
21
22
23
24
25
26
27
28
29
30
31

# Размещайте драгоценные камни случайным образом на self.grid
num_gems = 20
for i in range(num_gems):
gem_placed = False
while not gem_placed:
row = random.randint(self.player_limit,
self.grid_size - 1)
column = random.randint(
0, self.grid_size - 1)

80

Глава 6 - Пожиратель камней

32
33
34
35
36

if self.grid[row][column] == 0:
self.grid[row][column] =
random.randint(1, 3)
gem_placed = True
# добавляем

спрайты камней в группу драгоценных камней

37

# и расставляем их на доске

38

self.gem_group.add(StoneSprite(
self.colors[
self.grid[row][column]-1],
row, column,
self.piece_size,
self.grid[row][column]))

39
40
41
42
43

81

44
45
46
47
48
49

def check_for_gem(self, player):
if self.grid[player.row][player.column] > 0:
return True
else:
return False

50
51
52

def remove_gem(self, player):
self.grid[player.row][player.column] = 0

53
54
55

def get_cell_value(self, row, column):
return self.grid[row][column]

Игровые спрайты
Каждый игровой спрайт рисует объект в игре. Все спрайты имеют в
своей структуре следующий контракт:

1
2
3
4

class MySprite
__init__
def update(self)
def draw(self, surface)

Глава 6 - Пожиратель камней

82

Нам не всегда нужно реализовывать обновление, потому что
возможно, что игровой спрайт не двигается или изменяется
каким-либо образом. Например, камни после их создания не
меняются ни графически, ни в своем положении, поэтому нет
необходимости их обновлять. С другой стороны, спрайт игрока
движется с каждым нажатием клавиши, поэтому он должен
постоянно обновляться в игре при обнаружении клавиши со
стрелкой. Функция рисования используется для рисования спрайта,
поэтому она используется всегда. Давайте посмотрим на спрайт
для камня и спрайт для игрока:
Каменный спрайт ниже имеет большую часть кода в конструкторе.
Это потому, что как только мы определяем его образ, он никогда не
меняется. Даже отрисовка спрайта заранее предопределена в
конструкторе. Все, что нужно сделать функции отрисовки, — это
скопировать изображение, созданное в конструкции, и скопировать
значение драгоценного камня (1,2 или 3). Функция обновления
абсолютно ничего не делает, если она вызывается.
Давайте внимательнее посмотрим на конструктор (__init__),
поскольку именно здесь находится основная часть класса.
Конструктор создает объект шрифта и начинает с пустого
изображения 20x20. Затем он заполняет изображение белым
цветом и рисует на его поверхности закрашенный круг. Цвет круга
будет зависеть от цвета, переданного в спрайт (красный, зеленый
или синий). После того, как конструктор нарисует круг, он
отображает белый шрифт поверх круга и вставляет его в центр
круга. Наконец, он перемещает прямоугольник в строку и столбец,
переданные в конструктор. Перемещение прямоугольника
переместит весь камень в строку и столбец на доске.

1
2
3
4
5
6
7

class StoneSprite(pygame.sprite.Sprite):
def __init__(self, color, row, column,
piece_size, gem_value):
super().__init__()
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
small_font = pygame.font.Font(None, 16)

8
9

self.row = row

Глава 6 - Пожиратель камней

10

83

self.column = column

11
12

self.piece_size = piece_size

13

# Создаём поверхность для спрайта

14

self.image = pygame.Surface(
[piece_size, piece_size])
self.image.fill(WHITE)

15
16
17
18

# Рисуем прямоугольник на поверхности спрайта

19

pygame.draw.circle(self.image, color,
(piece_size/2, piece_size/2),
int(piece_size/2.2))
self.gem_value = small_font.render(
str(gem_value), True, WHITE)
self.image.blit(self.gem_value, (piece_size/3,
piece_size/4))
self.rect = self.image.get_rect().move(
column*piece_size,
row*piece_size)

20
21
22
23
24
25
26
27
28
29
30
31
32

def update(self):
pass

33
34
35
36
37

# Рисуем спрайт на экране
def draw(self, surface):
surface.blit(self.image, self.rect)
surface.blit(self.gem_value, self.rect)

Теперь давайте посмотрим на спрайт пожирателя камней. В этом
спрайте мы представляем новую библиотеку под названием
pyganim (анимация pygame). Вам нужно будет установить библиотеку
pyganim с помощью pip install:
1

pip install pyganim

Глава 6 - Пожиратель камней

84

Библиотека анимаций облегчает нам анимацию нашего
пожирателя камней без необходимости обрабатывать его в
игровом цикле. Способ, которым мы будем анимировать
пожирателя камней, заключается в том, чтобы он открывал и
закрывал рот, чтобы он выглядел так, как будто он ест камни
(что-то вроде PacMan!). Для этого нам нужны только два
изображения: едок с открытым ртом и едок с закрытым ртом.

Библиотека анимации pygame позволяет нам легко анимировать
это с помощью метода PygAnimation, который берет изображения в
паре со временем кадра в миллисекундах. Для нашего pacman мы
чередуем два изображения каждые 250 миллисекунд или четверть
секунды. Это даст нам желаемый эффект открывания и закрывания
рта пожирателя. Кроме того, поскольку наши изображения
довольно большие, нам нужно уменьшить их масштаб до размера
ячейки сетки. Мы могли бы сделать это вручную, изменив размер
изображения, или мы можем использовать функцию
масштабирования, предоставленную нам библиотекой анимации.
Мы решили уменьшить размер с помощью функции
масштабирования.
Чтобы воспроизвести анимацию, мы просто вызываем play для
объекта анимации, и он будет запускать анимацию открытия и
закрытия рта едоков на протяжении всей игры.

Глава 6 - Пожиратель камней

1
2

85

import pygame
import pyganim

3
4
5
6

class PlayerSprite(pygame.sprite.Sprite):
def __init__(self, row, column, piece_size):
super().__init__()

7
8
9
10
11
12
13
14
15
16
17

self.row = row
self.column = column
self.piece_size = piece_size
self.anim = pyganim.PygAnimation(
[("pacopen.png", 250), ("pacclose.png", 250)])
self.anim.scale((piece_size, piece_size))
self.anim.play()
self.rect = pygame.Rect(
column*piece_size, row*self.piece_size,
self.piece_size, self.piece_size)

18
19
20
21
22

def update(self):
self.rect = self.anim.getRect().move(
self.column*self.piece_size,
self.row*self.piece_size)

23
24
25

def draw(self, surface):
self.anim.blit(surface, self.rect)

Обратите внимание, что в объекте пожирателя функция
обновления не пуста. Причина этого в том, что каждый раз, когда в
игре нажимается клавиша со стрелкой, мы обновляем позицию
строки или столбца игроков. Мы должны вызвать update для
объекта player, чтобы переместить прямоугольник его положения в
новое положение ячейки сетки, определяемое нажатой клавишей.
Чтобы нарисовать игрока, мы просто перенесли анимированный
объект на игровую поверхность и поместили его в правильное
положение на игровом поле.
Другой спрайт, который мы часто обновляем, — это спрайт
времени. Этот спрайт покажет оставшееся время, оставшееся
пользователю в игре. В основном цикле мы обновляем время,
оставшееся после каждой итерации цикла:

Глава 6 - Пожиратель камней

1
2
3

86

# Время обновления
game_time.update_time(time_limit,
pygame.time.get_ticks() - start_time)

TimeSprite обновляет свое внутреннее время и позже использует
это время в функции обновления для отображения этого времени в
объекте шрифта, содержащем оставшееся время:

1

import pygame

2
3
4
5
6
7
8
9
10
11
12

class TimeSprite(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
BLACK = (0, 0, 0)
self.time = 0
self.small_font = pygame.font.Font(None, 16)
self.image = self.small_font.render(
f'Time: {self.time}', True, BLACK)
self.rect = self.image.get_rect().move(280, 15)

13
14
15
16

def update(self):
BLACK = (0, 0, 0)

17

# обновить изображение времени

18

self.image = self.small_font.render(
f'Time: {self.time}',
True, BLACK)
self.rect = self.image.get_rect().move(280, 15)

19
20
21
22
23
24

def draw(self, surface):

Глава 6 - Пожиратель камней

# Рисуем время на экране
surface.blit(self.image, self.rect)

25
26
27

def update_time(self, time_limit, time_in_millisecond\

28
29

s):

30
31
32
33

# рассчитать оставшееся время
calculated_time = int(time_limit (time_in_milliseconds / 1000))

34
35
36
37
38

# не нужно опускаться ниже 0
if calculated_time < 0:
calculated_time = 0
self.time = calculated_time

ScoreSprite похож на TimeSprite. Он содержит функцию для
обновления счета, а затем в функции обновления он создает
изображение для рисования счета с помощью self.score.

1

import pygame

2
3
4
5
6
7
8
9

class ScoreSprite(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
BLACK = (0, 0, 0)
self.score = 0
self.small_font = pygame.font.Font(None, 16)
#нужно исходное изображение для определения прямоугольника

11

self.image = self.small_font.render(
f'Score: {self.score}', True, BLACK)

12

# получить прямоугольник, ограничивающий счет

10

13

self.rect = self.image.get_rect().move(0, 0)

14
15
16

def update(self):
BLACK = (0, 0, 0)

87

Глава 6 - Пожиратель камней

18

self.image = self.small_font.render(
f'Score: {self.score}', True, BLACK)

19

# пересчитываем прямоугольник

20

# так как изображение изменилось
self.rect = self.image.get_rect().move(0, 0)

17

21

88

22
23
24
25

def draw(self, surface):
# Рисуем спрайт на экране
surface.blit(self.image, self.rect)

26
27
28
29

def update_score(self, score):
self.score = score

Отслеживание рекорда
HiScoreSprite отвечает за отслеживание рекордов игры. Он
отличается от Score Sprite тем, что запоминает наивысший балл и
обновляется только при достижении более высокого балла. Счет
хранится в файле, поэтому он запоминается, даже если
пользователь выключит свой компьютер.
Класс обряда HiScoreSp начинается с инициализации переменной
score, которая является наивысшим результатом, достигнутым
игроком. Он открывает файл с именем hiscore.txt и считывает
сохраненную в нем оценку, преобразует ее в целое число и сохраняет
как переменную оценки.
Затем класс настраивает отображение счета, создавая мелкий
шрифт и отображая текст «HiScore: [score]» в изображение.
Атрибуту rect присваивается позиция на экране, в данном случае
(150, 0). Метод update вызывает метод update_high_score для
обновления максимальной оценки. Подобно спрайту Score, метод
draw рисует на экране максимальный счет.
Метод update_high_score используется для обновления наивысшего
результата в игре. Он сравнивает новую оценку с текущей
наивысшей оценкой и, если новая оценка выше, обновляет
переменную оценки и текст, отображаемый на экране, записывает
новую оценку в файл hiscore.txt и сохраняет его. Если новый счет не
выше, он ничего не делает.

Глава 6 - Пожиратель камней

1

89

import pygame

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

class HiScoreSprite(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
f = open('files/hiscore.txt', 'r')
self.hi_score = int(f.read())
print(f'read hi score of {self.hi_score}')
f.close()
self.current_score = -1
self.small_font = pygame.font.Font(None, 16)
self.image = self.small_font.render(
f'HiScore: {self.hi_score}', True, BLACK)
self.rect = self.image.get_rect().move(150, 0)
#

Рисуем спрайт на экране

19
20
21

def update(self):
self.update_high_score(self.current_score)

22
23
24

def draw(self, surface):
surface.blit(self.image, self.rect)

25
26
27
28
29
30
31
32

def update_high_score(self, score):
BLACK = (0, 0, 0)
if self.hi_score < score:
self.hi_score = score
self.image = self.small_font.render(
f'HiScore: {self.hi_score}', True, BLACK)
self.rect = self.image.get_rect().move(150, 0)

Глава 6 - Пожиратель камней

33
34
35
36
37
38

90

print(f'write hi score of {self.hi_score}')
f = open('files/hiscore.txt', 'w')
f.write(str(score))
f.close()
else:
pass

Обнаружение столкновений
Существует встроенная функция rect.colliderect для обнаружения
столкновений между спрайтами. Мы можем перебрать все камни на
доске и использовать функцию colliderect, чтобы определить,
сталкивается ли один из камней с прямоугольником игрока на
доске.
Функция, которую мы создадим, называется detect_collisions и
принимает три аргумента:
playerSprite: объект PlayerSprite, представляющий персонажа
игрока.
group:группа StoneSprites, представляющая камни на доске.
piece_size:целое число, представляющее длину и ширину каждого
спрайта, измеренную в пикселях.
Цель функции — проверить, не столкнулся ли спрайт игрока с
каким-либо из спрайтов в группе.
Функция начинается с перебора всех спрайтов в группе с
использованием метода .sprites(). Для каждого спрайта он создает
прямоугольное представление спрайта игрока (playerRect) с
помощью конструктора pygame.Rect. Позиция спрайта игрока
вычисляется путем умножения его свойств строки и столбца на
piece_size, а размер прямоугольника устанавливается равным
(piece_size, piece_size).
Затем вызывается метод colliderect объекта playerRect для свойства
rect текущего объекта StoneSprite, который зацикливается. Этот
метод возвращает True, если прямоугольник спрайта игрока и
текущий прямоугольник спрайта пересекаются, что означает, что
произошло столкновение.

Глава 6 - Пожиратель камней

91

Если обнаружено столкновение, функция возвращает текущий
каменный спрайт. Если цикл завершается без обнаружения
коллизии, возвращается None, чтобы указать, что коллизии не
произошло.
1
2

def detect_collisions(playerSprite: PlayerSprite, group: \
pygame.sprite.Group, piece_size: int):

3
4

for sprite in group.sprites():

5

# обнаружить столкновение со спрайтом
playerRect = pygame.Rect((playerSprite.column *
piece_size,
playerSprite.row * piece_size),
(piece_size, piece_size))
if playerRect.colliderect(sprite.rect):
return sprite
return None

6
7
8
9
10
11
12

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

Космическое
вторжение в PyGame
Введение
Space Invaders — классическая видеоигра, разработанная
корпорацией Taito в 1978 году. Игра была разработана Томохиро
Нисикадо, вдохновленным популярной игрой Breakout и
классическим научно-фантастическим фильмом «Звездные войны».
Первоначально игра была выпущена в Японии, но быстро
завоевала популярность во всем мире, став культурным
феноменом в 1980-х годах. Простой игровой процесс и культовая
8-битная графика сделали ее фаворитом среди игроков и сделали
ее классикой эпохи аркад.
В игре игроки управляют космическим кораблем, который
должен побеждать волны инопланетных захватчиков,
спускающихся с верхней части экрана. Сложность игры
увеличивается по мере продвижения игрока, с более быстрыми
и агрессивными атаками пришельцев.
Space Invaders стала не только хитом игровых автоматов, но и
помогла запустить индустрию видеоигр и вызвала волну игр на
космическую тематику. За прошедшие годы она была перенесена на
множество платформ, включая домашние консоли и персональные
компьютеры, что обеспечило ей неизменную популярность и статус
игровой иконы.
В этой главе мы опишем, как воссоздать классическую игру с помощью
pygame.

93

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

94

95

Основной цикл
Подобно тому, как мы настроили наш основной цикл для игры с
пожирателем камней, нам нужно сделать то же самое для
вторжения в космос. Цикл состоит из реагирования на события
клавиатуры в игре, а также отрисовки различных спрайтов и
обнаружения столкновений между ними. Чтобы облегчить чтение
основного цикла, мы разбили код на несколько высокоуровневых
функций.
Первая функция высокого уровня, process_events, обрабатывает
события клавиатуры для перемещения и стрельбы игрока. Вот
другие функции высокого уровня и их описания:
handle_scoring - обновляет текущий счет на экране на основе счета
игроков. Эта функция также обновляет рекорд и индикатор жизней.
handle_alien_movement - эта функция перемещает инопланетян по
экрану и помогает инопланетянам изменить направление, когда они
достигают края экрана. Они также двигаются вниз каждый раз, когда
сталкиваются с краем.
handle_player_movement - обрабатывает движение игрока в
соответствии с нажатой клавишей со стрелкой. Стрелка влево
перемещает игрока влево, пока он не отпустит клавишу, и то же
самое со стрелкой вправо.
handle_bullet - эта функция обрабатывает активную пулю,
исходящую от игрока, пытающегося поразить инопланетян. Она
перемещает пулю вверх по экрану с определенной скоростью
каждый раз в игровом цикле. Он также проверяет, не столкнулась
ли пуля с инопланетянином, с помощью функции handle_alien_hit.
check_for_bomb_activation - эта функция проверяет на основе
случайно сгенерированного значения, сбросил ли инопланетянин
бомбу. Если инопланетянин сбросил бомбу, это записывается в
массив, чтобы позже обработать его движение.
handle_active_bombs перебирает массив бомб и рисует активные
бомбы. Также проверяет, попала ли бомба в игрока, вызывая
handle_player_hit.

96
draw_aliens - Рисует всех пришельцев. Инопланетяне рисуются
путем прохода по строкам пришельцев, хранящихся в Alien_groups,
а затем по каждому пришельцу в строке. Строка — это группа
pygame.
handle_alien_speedup - handle_alien_speedup — по мере того, как
игрок убивает все больше и больше пришельцев, скорость
пришельцев после определенного порога увеличивается. В
настоящее время этот порог наступает, когда пришельцев всего 25,
затем когда остается только 5 пришельцев, и, наконец, когда
остается только 1 пришелец.
1
2
3

while running:
(player_x, player_y) = player.position
window.fill(BLACK)

4
5

running = process_events()

# process the keyboard

6
7
8
9
10

if (game_over):
show_game_over_prompt()
pygame.display.flip()
continue

11
12

# подсчет очков

13

handle_scoring(window, score, player_score)

14
15

# переместить пришельцев

16

handle_alien_movement()

17
18

# переместить игрока

19

handle_player_movement(window_width, player_left,
player_right, player, player_x)

20
21
22

# переместить пулю

23

if bullet_active:
handle_bullet(bullet, bullet_active)

24
25
26

97
27
28

# проверять активацию бомбы каждые 2 секунды
check_for_bomb_activation()

29
30
31

# обновить активные бомбы
handle_active_bombs(active_bombs)

32
33
34

# рисуем пришельцев и проверяем на удаление
draw_aliens(window, alien_groups)

35
36
37

# проверь, не пора ли ускорить инопланетян

38

# в зависимости от количества оставшихся инопланетян
handle_alien_speedup(total_aliens)

39
40
41
42

# показать дисплей
pygame.display.flip()

43
44
45

# обновить время игры
game_time = pygame.time.get_ticks() - start_time

Игровые спрайты
Как и в случае с пожирателем камней, проще всего создать
космическое вторжение, создав спрайты. Эта игра содержит
следующие спрайты.
ImageSprite - базовый класс для всех спрайтов, которые
загружают изображение и отслеживают положение.
PlayerSprite - пушка игрока, которой вы управляете, для стрельбы
по инопланетянам.
BombSprite - спрайт, представляющий изображение бомбы,
сброшенной инопланетянином.
BulletSprite - спрайт, показывающий пулю, выпущенную по
захватчикам.
InvaderSprite - рисует захватчика, используя два изображения для
анимации его движения.
MessageSprite - используется для рисования игры поверх
сообщения.
ScoreSprite - отображает счет игры в верхней части экрана.
HighScoreSprite - тот же спрайт, который мы использовали в
пожирателе камней для отслеживания рекорда.
LivesSprite - Рисует количество оставшихся жизней у игрока

98

Спрайт игрока
Давайте сначала подробно рассмотрим спрайт игрока. Этот код
определяет класс с именем PlayerSprite, который расширяет
ImageSprite. Класс PlayerSprite представляет управляемый игроком
спрайт, который может двигаться влево или вправо, быть убитым и
взорваться. Он имеет следующие атрибуты:
dead: логический флаг, указывающий, мертв спрайт или нет.
speed: число с плавающей запятой, представляющее скорость, с
которой спрайт движется влево или вправо.
death_time: целое число, представляющее время, когда спрайт
был убит.
animate_explosion: объект PygAnim, представляющий анимацию
взрыва спрайта.
Класс PlayerSprite имеет следующие методы:
init(self, name, x, y): конструктор класса PlayerSprite. Он
инициализирует положение спрайта и устанавливает его атрибуты
в их начальные значения.
update: метод, который обновляет позицию спрайта.
kill: метод, который убивает спрайт, запуская анимацию взрыва.
draw: метод, рисующий спрайт на заданной поверхности. Если
спрайт не умер, он вызывает метод draw() суперкласса для
рисования изображения спрайта. Если спрайт мертв, он
воспроизводит анимацию взрыва и проверяет, прошло ли
достаточно времени для завершения взрыва.
move_left: метод, который перемещает спрайт влево, обеспечивая
его положение.
move_right: метод, который перемещает спрайт вправо, регулируя
его положение.

99
1
2
3

import pygame
import pyganim
from ImageSprite import ImageSprite

4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

class PlayerSprite(ImageSprite):
def __init__(self, name, x, y):
super().__init__(name, x, y)
self.dead = False
self.speed = .1
self.death_time = 0
self.animate_explosion = pyganim.PygAnimation(
[("images/shipexplosion/frame1.gif", 250),
("images/shipexplosion/frame2.gif", 250),
("images/shipexplosion/frame3.gif", 250),
("images/shipexplosion/frame4.gif", 250),
("images/shipexplosion/frame5.gif", 250),
("images/shipexplosion/frame6.gif", 250),
("images/shipexplosion/frame7.gif", 250),
("images/shipexplosion/frame8.gif", 250),],
loop=False)

21
22
23
24

# просто вызовите суперкласс, чтобы настроить прямоугольник
def update(self):
super().update()

25
26

# Рисуем спрайт на экране

27

def kill(self):
self.animate_explosion.play()
self.dead = True
self.death_time = pygame.time.get_ticks()

28
29
30
31
32
33
34
35

def draw(self, surface):
if not self.dead:
super().draw(surface)

100
36
37
38
39
40
41

else:
self.animate_explosion.blit(surface,
self.rect)
if (pygame.time.get_ticks() self.death_time) > 5000:
self.dead = False

42
43
44
45

def move_left(self):
(x, y) = self.position
self.position = (x - self.speed, y)

46
47
48
49

def move_right(self):
(x, y) = self.position
self.position = (x + self.speed, y)

Взрыв игрока
Объект animate_explosion использует библиотеку pyganim для
управления анимацией взрыва корабля, быстро отрисовывая
каждый из 8 кадров взрыва один раз. Анимация инициализируется
со всей информацией, необходимой для воспроизведения кадров
взрыва, и о том, как долго будет отображаться каждый кадр:

1
2
3
4
5
6
7
8
9
10

self.animate_explosion = pyganim.PygAnimation(
[("images/shipexplosion/frame1.gif",
("images/shipexplosion/frame2.gif",
("images/shipexplosion/frame3.gif",
("images/shipexplosion/frame4.gif",
("images/shipexplosion/frame5.gif",
("images/shipexplosion/frame6.gif",
("images/shipexplosion/frame7.gif",
("images/shipexplosion/frame8.gif",
loop=False)

250),
250),
250),
250),
250),
250),
250),
250),],

101
Чтобы воспроизвести анимацию, мы просто вызываем метод play
для animate_-explosion внутри нашего метода kill:

1
2
3
4

def kill(self):
self.animate_explosion.play()
self.dead = True
self.death_time = pygame.time.get_ticks()

Invader Sprite - Захватчик Спрайт
Далее давайте взглянем на InvaderSprite, который рисует
анимированного инопланетянина, движущегося по экрану. Класс
InvaderSprite наследуется от pygame.sprite.Sprite, который является
базовым классом для всех спрайтов в Pygame. Метод init()
инициализирует различные переменные экземпляра, такие как два
спрайта изображения (name1 и name2), которые используются для
рисования и анимации пришельца, спрайт изображения взрыва,
родительская строка, представленная группой спрайтов, скорость
пришельца, его текущее направление (влево или вправо), его
начальное положение и его точки. Метод update() обновляет
положение двух спрайтов изображения, представляющих
инопланетянина, в зависимости от его текущего положения. Метод
draw() рисует текущий спрайт изображения на поверхности игры.
Методы move_left(), move_right() и move_down() перемещают
инопланетянина влево, вправо или вниз соответственно. Метод
switch_image() переключается между двумя спрайтами изображения
инопланетянина в зависимости от номера изображения,
переданного в этот метод. Методы get_width() и get_height()
возвращают ширину и высоту текущего спрайта изображения
инопланетянина. Метод kill() переключает спрайт изображения на
спрайт взрыва и помечает инопланетянина как мертвого.

102
1
2
3

import pygame
from ImageSprite import ImageSprite
from BombSprite import BombSprite

4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

class InvaderSprite(pygame.sprite.Sprite):
def __init__(self, name1, name2, x, y, parent, points\
):
super().__init__()
self.imageSprite1 = ImageSprite(name1, x, y)
self.imageSprite2 = ImageSprite(name2, x, y)
self.explosion =
ImageSprite('explosion', x, y)
self.imageSprite = self.imageSprite1
self.parent = parent
self.speed = .01
self.currentDirection = 'right'
self.position = (x, y)
self.rect = self.imageSprite.image.get_rect()
.move(self.position)
self.dead = False
self.death_time = 0
self.bomb_active = False
self.points = points

24
25

# обновить положение двух спрайтов,

26

# представляющих инопланетянина

27

def update(self):
self.imageSprite.rect = self.imageSprite
.image.get_rect()
.move(self.position)
self.imageSprite1.rect = self.imageSprite.rect
self.imageSprite2.rect = self.imageSprite.rect

28
29
30
31
32
33
34
35

# Рисуем спрайт на экране

103
36
37

def draw(self, surface):
self.imageSprite.draw(surface)

38
39
40
41

def move_left(self):
(x, y) = self.position
self.position = (x - self.speed, y)

42
43
44
45

def move_right(self):
(x, y) = self.position
self.position = (x + self.speed, y)

46
47
48
49

def move_down(self):
(x, y) = self.position
self.position = (x, y + 10)

50
51
52
53
54
55
56
57

#переключение между двумя изображениями, изображающими инопланетянина
def switch_image(self, imageNumber):
if self.dead == True: return
if (imageNumber == 1):
self.imageSprite = self.imageSprite1
else:
self.imageSprite = self.imageSprite2

58
59
60

def get_width(self):
return self.imageSprite.get_width()

61
62
63

def get_height(self):
return self.imageSprite.get_height()

64
65
66
67
68
69
70

def kill(self):
self.imageSprite = self.explosion
self.imageSprite.draw(self.imageSprite.image)
self.imageSprite.update()
self.dead = True
self.death_time = pygame.time.get_ticks()

104

Как движется инопланетянин?
Класс InvaderSprite использует здесь трюк, чтобы выполнить анимацию
открытия и закрытия инопланетянина. В любой момент
времени свойство self.imageSprite содержит ссылку либо на
изображение инопланетянина с открытой клешней, либо на
изображение пришельца с закрытой клешней. Программа либо
передает 1, либо 0 в метод switch_image и соответственно присваивает
спрайту
изображения
два
или
одно
из
двух
изображений. Когда придет время рисовать инопланетянина,
независимо от того, чему назначен imageSprite в это время, будет тот,
который будет нарисован на поверхности.

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

1
2
3
4
5
6
7
8

class BulletSprite(pygame.sprite.Sprite):
def __init__(self, x, y, bullet_width,
bullet_height, speed):
super().__init__()
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
BLACK = (0, 0, 0)
small_font = pygame.font.Font(None, 16)

9
10
11

self.position = (x, y)
self.speed = speed

12
13
14
15
16

# Создаём поверхность для спрайта
self.image = pygame.Surface(
[bullet_width, bullet_height])
self.image.fill(GREEN)

105
17

# Рисуем прямоугольник на поверхности спрайта

18

self.rect = self.image.get_rect().move(x, y)

19
20

# перемещаем спрайт в соответствии с положением пули

21

def update(self):
(x, y) = self.position
self.rect = self.image.get_rect().move(x, y)

22
23
24
25

# Рисуем спрайт на экране

26

def draw(self, surface):
surface.blit(self.image, self.rect)

27

Спрайт Бомба
Спрайт-бомба — это спрайт, который падает с инопланетян. Он
наносится на поверхность изображения в виде ряда
диагностических белых линий, используемых для формирования
формы молнии.
1
2
3
4
5
6
7
8

class BombSprite(pygame.sprite.Sprite):
def __init__(self, x, y, bullet_width,
bullet_height, speed, parent):
super().__init__()
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
BLACK = (0, 0, 0)
small_font = pygame.font.Font(None, 16)

9
10
11
12

self.position = (x, y)
self.speed = speed
self.parent = parent

13
14

# Создаём поверхность для спрайта

15

self.image = pygame.Surface(
[bullet_width, bullet_height])

16

106
17
18

pygame.draw.lines(self.image, WHITE, True,
[(0, 0), (5, 5), (0, 10), (10, 15)], 1)

19
20

# Рисуем прямоугольник на поверхности спрайта

21

self.rect = self.image.get_rect().move(x, y)

22
23
24
25
26

# обновить бомбу в соответствии с текущей позицией

def update(self):
(x, y) = self.position
self.rect = self.image.get_rect().move(x, y)

27
28

# Рисуем спрайт на экране

29
30
31

def draw(self, surface):
surface.blit(self.image, self.rect)

107

Перемещение игрока

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

108
1
2
3
4
5
6

def process_events():
global player_left, player_right
(player_x, player_y) = player.position
running = True
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:

7

# Проверяем, двигался ли игрок

8

if event.key == pygame.K_LEFT
and player_x > 0:
player_left = True
player_right = False
elif event.key == pygame.K_RIGHT and
player_x < window_width:
player_right = True
player_left = False
elif event.type == pygame.KEYUP:
player_left = False
player_right = False
return running

9
10
11
12
13
14
15
16
17
18
19

Внутри нашего игрового цикла мы используем флаги player_left и
player_right, установленные методом process_events, для
перемещения игрока. Мы консолидируем поведение игрока в
методе handle_player_movement, который принимает параметры,
необходимые для перемещения спрайта игрока. Обратите
внимание, что мы проверяем границы и если игрок выйдет за
границы экрана, мы не разрешаем движение. Также обратите
внимание, что если игрок помечен как мертвый, нам не нужно его
перемещать. Игрок перемещается со скоростью, соответствующей
скорости игрока. Как только скорость добавляется или вычитается
из положения игрока, спрайт игрока обновляется и
перерисовывается.

109
1
2
3
4
5
6
7
8
9
10
11

def handle_player_movement(window_width, player_left,
player_right, player, player_x):
if (player.dead):
pass
elif player_left:
if (player_x - player.speed) > 0:
player.move_left()
elif player_right:
if (player_x + player.speed) <
window_width -player.get_width():
player.move_right()

12
13
14

player.update()
player.draw(window)

110

Выстрел пулей

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

8

def process_events():
global player_left, player_right, bullet_active
(player_x, player_y) = player.position
running = True
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:

9

#Проверяем, двигался ли игрок

1
2
3
4
5
6
7

10

if event.key == pygame.K_UP:

111
11
12
13
14
15
16
17

if bullet_active == False:
bullet_active = True
bullet.position = (player_x + 30,
player_y - 20)
bullet_fire_sound.play()
...
return running

После того, как мы активировали пулю, мы можем обрабатывать ее
состояние и движение в методе основного цикла с именем
handle_bullet. В основном цикле, если пуля активна, мы вызываем
handle_bullet для рисования движущейся пули. Обработчик пули
принимает спрайт пули и флаг bullet_active, которые мы установили в
методе process_events. Y-позиция пули устанавливается путем
вычитания скорости пули из текущей y-позиции пули и обновления
позиции пули. Если позиция bullet_y находится за пределами
верхней части экрана (при y=0), мы устанавливаем для флага
bullet_active значение false.

1
2
3
4
5
6
7
8
9

def handle_bullet(bullet, bullet_active):
(bullet_x, bullet_y) = bullet.position
bullet_y = bullet_y - bullet.speed
bullet.position = (bullet_x, bullet_y)
bullet.update()
bullet.draw(window)
if (handle_alien_hit(bullet_x, bullet_y)):
bullet_active = False
bullet.position = (0, 0)

10
11
12

if (bullet_y < 0):
bullet_active = False

13
14

return bullet_active

112

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

def handle_alien_hit(bullet_x, bullet_y):
global gems_collected, player_score, bullet,
alien_groups
for alien_group in alien_groups:
for alien in alien_group:
(x, y) = alien.position
if bullet_x > x and
and bullet_x < x + alien.get_width()
and bullet_y > y and
bullet_y < y + alien.get_height():
alien.kill()
alien.death_time = pygame.time.get_ticks()
alien_dying.play()
player_score += alien.points
return True
return False

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

113
1
2
3
4
5
6
7
8

class InvaderSprite(pygame.sprite.Sprite):
...
def kill(self):
self.imageSprite = self.explosion
self.imageSprite.draw(self.imageSprite.image)
self.imageSprite.update()
self.dead = True
self.death_time = pygame.time.get_ticks()

В нашем основном цикле мы вызываем check_for_removal каждый
раз в цикле. Если инопланетянин мертв более 1/4 секунды (или 250
миллисекунд), то мы удаляем его из строки. Если из ряда выбиты
все пришельцы, то удаляем сам ряд.

1
2
3
4
5
6
7

def check_for_removal(alien):
if alien.death_time > 0
and alien.death_time + 250 <
pygame.time.get_ticks():
alien.parent.remove(alien)
if (len(alien.parent) == 0):
alien_groups.remove(alien.parent)

114

Рисование инопланетян

Чтобы обнаружить попадание инопланетян, вам нужно, чтобы
инопланетяне попали! В этом разделе мы опишем, как нарисованы
инопланетяне. Мы уже рассмотрели спрайт захватчика, следующим
шагом будет отрисовка всех видов захватчиков в разных рядах на
экране с помощью класса InvaderSprite. Обратите внимание, что два
нижних ряда захватчиков представляют собой один и тот же спрайт
и приносят 10 очков, а 2-й и третий ряды — это разные типы
захватчиков, приносящие 20 очков. Верхний ряд также является
уникальным захватчиком стоимостью 30 очков.
Сначала мы создадим конфигурацию пришельцев, которую вы
видите на рисунке выше, используя метод create_aliens. В нашем
игровом цикле мы будем обновлять движение пришельцев с
помощью функции handle_alien_movement и рисовать пришельцев с
помощью метода draw_aliens.
create_aliens рисует все 5 рядов пришельцев. Код определяет список
с именем Alien_names, который содержит имена разных типов
пришельцев в игре. Каждый тип имени пришельца относится к
образу пришельца, созданному с помощью InvaderSprite. Для
каждого InvaderSprite есть два изображения файла: открытый
инопланетный спрайт и закрытый инопланетный спрайт,
оканчивающийся на c.

115

Функция create_aliens() используется для создания настоящих
пришельцев в игре. Она начинается с создания пустого списка с
именем Alien_groups.
Затем функция использует цикл для создания пяти рядов
пришельцев. В каждой строке функция использует еще один цикл
для создания 11 инопланетян в этой строке. При создании
пришельца он конструирует InvaderSprite с открытой и закрытой
версиями конкретного пришельца.
Конструктор InvaderSprite также назначает каждому инопланетянину
позицию на экране для InvaderSprite, которая определяется его
строкой и столбцом в сетке пришельцев. Также в конструкторе
назначается родительская группа и очки, которые инопланетянин
получает при попадании в него. Каждый инопланетянин
добавляется в объект pygame.sprite.Group(), который является
контейнером для нескольких спрайтов в Pygame.
Наконец, каждый объект pygame.sprite.Group() добавляется в список
Alien_-groups, который используется для отслеживания всех строк
пришельцев в игре.
В целом, этот код настраивает различные типы инопланетян в игре
и создает их на экране в виде сетки, используя функциональные
возможности спрайтов Pygame.

1
2
3
4
5
6

## словарь для подсчета очков
score_dict = {
'invader1': 30,
'invader2': 20,
'invader3': 10
}

7
8
9

alien_names = ['invader1', 'invader2', 'invader2', 'invad\
er3', 'invader3' ]

116
10
11
12
13
14
15
16
17
18
19
20
21

def create_aliens():
global alien_groups
alien_groups = []
for i in range(0, 5):
alien_group = pygame.sprite.Group()
for j in range(0, 11):
alien = InvaderSprite(alien_names[i],
alien_names[i] + 'c',
30 + (j * 60), 60 + i*60, alien_group,
score_dict[alien_names[i]])
alien_group.add(alien)
alien_groups.append(alien_group)

22

Итак, мы нарисовали наши ряды инопланетян, теперь как их
перемещать? Для этого мы вызываем handle_alien_movement в
нашем основном цикле. Этот метод начинается с поиска самого
левого пришельца и самого правого пришельца. Причина, по
которой он их находит, заключается в том, что ему нужно знать,
какой инопланетянин заставит пришельцев изменить направление
и спуститься на ступеньку ниже. Нам также нужно знать самого
нижнего инопланетянина, чтобы сказать, когда пришельцы
приземляются. Функция move_aliens, вызываемая внутри
handle_alien_-motion, выполняет реальное движение
инопланетянина, которое мы обсудим чуть позже. Следующая
часть кода перебирает всех пришельцев и выполняет анимацию
открывания и закрывания когтей. Цикл вызывает switch_image для
каждого спрайта захватчика и передает общее игровое время,
деленное на частоту моргания, а затем на модуль 2, который будет
генерировать 1 или 0. 1 или 0 представляют, открывает ли
пришелец свои когти или закрывает их. Чем выше частота
моргания, тем медленнее инопланетянин будет открывать и
закрывать когти. Позже мы изменим скорость моргания по мере
уменьшения популяции пришельцев, чтобы ускорить анимацию
пришельцев.
Мы также можем использовать игровое время, чтобы определить,
когда воспроизводить инопланетный звук, когда он перемещается
по экрану. Мы проверяем модуль py game_time 400, чтобы
воспроизводить звук примерно каждые 1/2 секунды, когда результат
равен нулю. Последний бит кода устанавливает позицию всех
пришельцев, определяемую флагами, вычисленными методом
move_aliens. Независимо от того, на что установлены флаги, все
инопланетяне будут следовать направлению этих флагов,
поскольку все пришельцы движутся по экрану в тандеме.

117

Последний фрагмент кода py move_aliens_down = False устанавливает
флаг, который указывает инопланетянам двигаться вниз, в значение
false. Мы должны сбросить этот флаг после того, как мы
уже переместили инопланетян вниз для того, чтобы пришельцы
переместились только на одну строку вниз, а затем продолжили
движение влево или вправо. В противном случае инопланетяне
двинулись бы вниз довольно быстро!

1
2
3
4
5
6
7
8
9
10
11
12
13

def handle_alien_movement():
global game_time, move_aliens_down, alien_groups,
move_aliens_right
alien_rightmost = find_rightmost_alien()
alien_leftmost = find_leftmost_alien()
alien_bottommost = find_bottommost_alien()
(move_aliens_right, move_aliens_down) =
move_aliens(
alien_leftmost,
alien_rightmost,
alien_bottommost,
move_aliens_right,
move_aliens_down)

14
15

# делаем анимацию

16

for alien_group in alien_groups:
for next_alien in alien_group:
next_alien.switch_image(
int(game_time/blink_speed) % 2 )
next_alien.update()

17
18
19
20
21
22
23
24

#проигрывать инопланетный звук каждые полсекунды
if game_time % 400 == 0 and aliens_exist():
alien_movement.play()

118
25

for alien_group in alien_groups:
for alien in alien_group:
(x,y) = alien.position
if move_aliens_right:
alien.move_right()
else:
alien.move_left()
if move_aliens_down:
alien.move_down()
alien.update()

26
27
28
29
30
31
32
33
34
35
36
37

# сбросить перемещение инопланетянина вниз, мы только хотим,

38

#

39

чтобы они переместились на одну строку вниз.
move_aliens_down = False

move_aliens, показанный ниже, показывает, как мы вычисляем
определение движения множества всех пришельцев. Сначала мы
получаем положение самого дальнего левого инопланетянина с
именем first_alien и самого дальнего и самого правого
инопланетянина с именем last_alien. Затем, если мы в данный
момент двигаем пришельцев вправо, мы проверяем, попали ли
пришельцы в правую часть экрана. Если они наткнулись на границу,
двигаясь вправо, пришло время сменить направление и двигаться
вниз. В результате, если инопланетянин врежется в правую стену,
мы установим для флага move_right значение false, чтобы показать,
что теперь мы движемся влево. Мы также установим флаг
move_down в значение true, если, конечно, мы уже не достигли
нижней части экрана.
Мы также делаем аналогичную проверку координат first_alien, если
мы в данный момент движемся влево (или не движемся вправо).
если первая позиция пришельца выходит за пределы левой части
экрана, пришло время изменить направление пришельцев и
переместить их вправо и вниз.

119
1
2
3

def move_aliens(leftmost, rightmost, bottommost, move_rig\
ht, move_down):
global game_time

4
5
6

last_alien = rightmost
first_alien = leftmost

7
8

# ничего не делать, если первый и последний инопланетянин пуст

9
10
11

if (last_alien is None) or (first_alien is None):
return (move_right, move_down)

12
13

# получить координаты позиции для первого

14

# и последнего инопланетянина
(last_alien_x, last_alien_y) = last_alien.position
(first_alien_x, first_alien_y) = first_alien.position

15
16
17
18

# если мы уже движемся вправо, определить, должны ли мы

19

# продолжать или двигаться вниз и в обратном направлении

20
21
22
23
24
25

if move_right:
if last_alien_x + last_alien.speed >=
window_width - (last_alien.rect.width + 5):
move_right = False
if last_alien_y + last_alien.speed <

26
27
28
29
30

window_height - last_alien.rect.height:
if (bottommost.position[1] <
window_height - 50):
move_down = True

31
32

return move_right, move_down

33
34

# если мы уже движемся влево, определить должны ли мы

35

# продолжать или двигаться вниз и в обратном направлении\

120
36
37
38
39
40
41
42
43
44

ion
if not move_right:
if first_alien_x - first_alien.speed 0):
if (len(active_bombs) > 0):
for bomb in bombs_to_remove:
active_bombs.remove(bomb)

20
21
22
23

Ускорение движения пришельцев
По мере прохождения игры мы хотим бросить вызов игроку, угрожая
приземлиться инопланетянам быстрее, когда они перемещаются по
экрану. Мы создали метод handle_alien_-speedup и вызвали его в
основном цикле, чтобы увеличить скорость пришельцев в
зависимости от того, сколько пришельцев осталось. Приведенный
ниже код будет получать общее количество пришельцев, вызывая
total_aliens, и на основе этого общего количества мы изменим
скорость пришельцев. Если инопланетяне достигли в общей
сложности 20, 5 или 1, скорость пришельцев будет увеличена, а
также скорость анимации открытия и закрытия когтей пришельцами
(это делает пришельцев более угрожающими!)

1
2
3

def handle_alien_speedup(total_aliens):
global blink_speed, bomb_frequency,
first_speed_up, second_speed_up, third_speed_up

4
5
6
7
8
9
10

if (total_aliens() == 20):
if first_speed_up == False:
blink_speed = 200
bomb_frequency = 10
speed_up_aliens()
first_speed_up = True

124
11
12
13
14
15
16
17

if (total_aliens() == 5):
if second_speed_up == False:
blink_speed = 100
bomb_frequency = 20
speed_up_aliens()
second_speed_up = True

18
19
20
21
22
23
24

if (total_aliens() == 1):
if third_speed_up == False:
bomb_frequency = 40
blink_speed = 50
speed_up_aliens(2.0)
third_speed_up = True

Предоставленный код определяет функцию под названием
speed_up_aliens, которая принимает необязательный аргумент-фактор
со значением по умолчанию 1,0. Эта функция перебирает всех
пришельцев, которые существуют в разных группах пришельцев, и
увеличивает их скорость на коэффициент, который рассчитывается
с использованием заданного параметра фактора. Если параметр
фактора не передан, скорость всех пришельцев во всех группах
будет увеличена на фиксированный коэффициент 0,01. Поскольку
начальная скорость всех пришельцев равна 0,01, первое ускорение
удваивает скорость пришельцев.
Функция speed_up_aliens сначала проходит по каждой группе Alien_- в
списке Alien_groups, который представляет каждую строку
пришельцев. В пределах каждой Alien_group функция перебирает
каждого пришельца в этой группе, а затем обновляет атрибут
скорости пришельца, добавляя результат 0,01, умноженный на
значение коэффициента, к текущей скорости. Результат этого
расчета будет добавлен к текущему значению атрибута Alien.speed,
эффективно увеличив скорость пришельца на рассчитанный
коэффициент.
Обратите внимание, что параметр factor является необязательным,
и функция все равно будет работать, даже если аргумент не
передан. В этом случае будет использоваться значение
коэффициента по умолчанию 1,0, и скорость всех пришельцев будет
увеличена на 0,01 * 1,0 или 0,01. Если в функцию передано значение
фактора, скорость всех инопланетян вместо этого будет увеличена
на этот фактор. Последний инопланетянин ускорен в 3 раза по
сравнению с исходной скоростью (0,01 + 0,01 * 2,0).

125

1
2
3
4

def speed_up_aliens(factor = 1.0):
for alien_group in alien_groups:
for alien in alien_group:
alien.speed = alien.speed + .01 * factor

Добавление подсчета очков
Когда мы играем в игру, мы хотели бы отслеживать определенную
статистику для пользователя и отображать ее. В игре о
космическом вторжении мы будем отслеживать Score, High Score и
Lives. Мы уже создали Score и HiScore Sprites в нашей игре с
пожирателем камней, и мы можем использовать те же классы в
нашей игре о космическом вторжении. Жизни — единственный
новый класс, показанный ниже. Этот класс покажет, сколько жизней
осталось, в виде 3 значков кораблей, отображаемых в правом
верхнем углу. Когда корабль уничтожается, один значок корабля
удаляется до тех пор, пока не останется ни одного, после чего игра
заканчивается.
1

import pygame

2
3
4
5
6
7
8
9
10
11
12
13

class LivesSprite(pygame.sprite.Sprite):
def __init__(self, window_width):
super().__init__()
WHITE = (255, 255, 255)
self.window_width = window_width
self.lives = 3
self.livesImage = pygame.image.load(
'images/man.gif')
self.livesImage =
pygame.transform.scale(self.livesImage,
(40, 32))

126
self.rect = pygame.Rect(0, 0, 0, 0)
self.small_font = pygame.font.Font(None, 32)
self.image = self.small_font.render(
f'Lives: {self.lives}', True, WHITE)

14
15
16
17

# Рисуем спрайт на экране

18
19

def update(self):
WHITE = (255, 255, 255)
self.image = self.small_font.render(
f'Lives:', True, WHITE)
self.rect = self.image.get_rect()
.move(self.window_width - 250, 0)

20
21
22
23
24
25
26

def draw(self, surface):
surface.blit(self.image, self.rect)
for i in range(self.lives):
surface.blit(self.livesImage,
(self.window_width - 180 + i * 50, 0))

27
28
29
30
31
32

def update_lives(self, lives):
self.lives = lives

33
34

Мы можем использовать то же изображение, что и для нашего
игрока, как и для нашего индикатора жизней, нам просто нужно
уменьшить его с помощью метода pygame.transform.scale.
Вот подробное объяснение кода:
1.

2.

class LivesSprite(pygame.sprite.Sprite): определяет новый класс
под названием LivesSprite, который наследуется от
pygame.sprite.Sprite.
def init(self, window_width): определяет конструктор для
класса LivesSprite. Он принимает ширину окна в качестве
параметра, помогающего определить, где разместить
индикатор в верхней части экрана.

127
3.

4.
5.
6.
7.

8.

9.
10.
11.

12.
13.

14.

15.

super().init() инициализирует суперкласс
(pygame.sprite.Sprite), чтобы убедиться, что класс LivesSprite
наследует все необходимые атрибуты и методы.
WHITE = (255, 255, 255) определяет кортеж белого
цвета, используемый для рендеринга текста.
self.window_width = window_width хранит ширину окна в
переменной экземпляра.
self.lives = 3 инициализирует количество жизней равным
3.
self.livesImage = pygame.image.load(‘images/man.gif)
загружает изображение для представления жизни игрока
из файла ‘images/man.gif’.
self.livesImage = pygame.transform.scale(self.livesImage,
(40,32)) уменьшает изображение жизни до размера (40,
32).
self.rect = pygame.Rect(0, 0, 0, 0) инициализирует
прямоугольную область для отображения текста жизней.
self.small_font = pygame.font.Font(None, 32) создает
объект afont размером 32 для рендеринга текста Lives.
self.image = self.small_font.render(f’Lives:
{self.lives}’,True, WHITE) отображает исходный текст для
отображения жизней.
def update(self): определяет метод обновления,
отображаемого текста Lives.
def draw(self, surface): определяет метод рисования,
который рисует текст отображения жизней и изображения
жизней на экране. Параметр surface - это поверхность, на
которой отображается отображение жизней.
def update_lives(self, lives): определяет метод
update_lives, который обновляет количество жизней из
основного цикла, когда игрок умирает. Параметр жизни это новое количество жизней.
Таким образом, класс LivesSprite отвечает за отображение
количества жизней игрока в игре Space Invaders. Он
обрабатывает рендеринг как текста, так и живых
изображений на игровом экране.

128

В методе handle_player_hit внутри нашей основной программы, если
в игрока попала инопланетная бомба, мы уменьшаем количество
жизней и обновляем LivesSprite, чтобы отразить потерянную жизнь:

1
2
3
4

handle_player_hit(bomb_x, bomb_y):
...
player_lives = player_lives - 1
lives_indicator.update_lives(player_lives)

129

Запуск НЛО

В классической аркадной игре Space Invaders тарелка, также
известная как НЛО или корабль-загадка, появляется в верхней
части экрана и перемещается по экрану горизонтально через
равные промежутки времени.Тарелка обычно появляется каждые
25–30 секунд, но это может варьироваться в зависимости от
конкретной версии игры или этапа, на котором находится игрок.
Цель тарелки - дать игроку возможность заработать бонусные очки,
сбивая ее, когда она перемещается с одной стороны экрана на
другую. В нашей версии мы будем запускать тарелку каждые 20
секунд.
Чтобы запрограммировать это, мы начнем с создания SaucerSprite.
Спрайт будет анимирован с использованием 3 изображений
тарелок, которые создадут иллюзию вращения тарелки. Вот
разбивка кода SaucerSprite:

130

Вот разбивка класса и его методов:











init: метод-конструктор инициализирует спрайт тарелки
тремя разными изображениями (name1, name2 и name3) для
тарелки, начальной позицией (x, y) и необязательным
параметром уровня. Он устанавливает начальный спрайт
изображения, генерирует случайный счет для тарелки,
вычисляет скорость на основе уровня и инициализирует
другие соответствующие атрибуты.
reset: этот метод сбрасывает тарелку на заданное
положение и уровень, обновляя скорость, очки и другие
атрибуты.
update: этот метод сбрасывает тарелку на заданное
положение и уровень, обновляя скорость, очки и другие
атрибуты.
draw: Этот метод рисует спрайт тарелки на заданной
поверхности. Если тарелка мертва, он рисует счет тарелки
вместо спрайта.
move_left: этот метод перемещает спрайт тарелки влево
наего значение скорости, при условии, что тарелка не
мертва.
switch_image: этот метод переключает спрайт изображения
тарелки между тремя предоставленными изображениями на
основе заданного номера изображения.
get_width и get_height: эти методы возвращают ширину и
высоту спрайта-блюдца соответственно.
kill: этот метод устанавливает для атрибута dead блюдца
значение True и записывает время, когда блюдце было убито,
с помощью функции get_ticks() Pygame. Причина, по которой он
записывает время смерти, заключается в том, чтобы дать
спрайту время показать количество очков, набранных на
экране после того, как тарелка умрет.

Вот полный код спрайта с тарелкой:

Space Invasion в PyGame

1
2
3

131

import random
import pygame
from ImageSprite import ImageSprite

4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

class SaucerSprite(pygame.sprite.Sprite):
def __init__(self, name1, name2, name3, x, y, level =\
1):
super().__init__()
self.active = False
self.imageSprite1 = ImageSprite(name1, x, y)
self.imageSprite2 = ImageSprite(name2, x, y)
self.imageSprite3 = ImageSprite(name3, x, y)
self.imageSprite = self.imageSprite1
self.explosion = pygame.font.Font(None, 32)
self.imageSprite = self.imageSprite1
self.points = random.randint(1, 6) * 50
self.saucerScore = self.explosion.render(
str(self.points), True, (255, 255, 255))
self.speed = .05 * (.9 + level/10.0)
self.position = (x, y)
self.rect = self.imageSprite.image
.get_rect().move(self.position)
self.dead = False
self.death_time = 0

26
27
28
29
30
31
32
33
34
35

def reset(self, x, y, level = 1):
self.imageSprite = self.imageSprite1
self.points = random.randint(1, 6) * 50
self.saucerScore = self.explosion
.render(str(self.points), True,
(255, 255, 255))
self.speed = .05 * (.9 + level/10.0)
self.currentDirection = 'left'
self.position = (x, y)

Space Invasion в PyGame

36
37
38
39

self.rect = self.imageSprite.image
.get_rect().move(self.position)
self.dead = False
self.death_time = 0

40
41

# обновить положение 3-х спрайтов,

42

# представляющих НЛО

43

def update(self):
self.rect = self.imageSprite.rect
if self.dead == True: return
self.imageSprite.rect = self.imageSprite.image
.get_rect().move(self.position)
self.imageSprite1.rect = self.imageSprite.rect
self.imageSprite2.rect = self.imageSprite.rect
self.imageSprite3.rect = self.imageSprite.rect
self.rect = self.imageSprite.rect

44
45
46
47
48
49
50
51
52
53

# Рисуем спрайт на экране

54
55
56
57
58
59

def draw(self, surface):
if self.dead == True:
surface.blit(self.saucerScore, self.rect)
else:
self.imageSprite.draw(surface)

60
61
62
63
64

def move_left(self):
if self.dead == True: return
(x, y) = self.position
self.position = (x - self.speed, y)

65
66
67
68
69
70

#переключение между 3 изображениями, представляющими тарелку
def switch_image(self, imageNumber):
if self.dead == True: return
if (imageNumber == 1):
self.imageSprite = self.imageSprite1

132

Space Invasion в PyGame

71
72
73
74

133

elif (imageNumber == 2):
self.imageSprite = self.imageSprite2
else:
self.imageSprite = self.imageSprite3

75
76
77
78

def get_width(self):
return self.imageSprite.get_width()

79
80
81

def get_height(self):
return self.imageSprite.get_height()

82
83
84
85

def kill(self):
self.dead = True
self.death_time = pygame.time.get_ticks()

Как мы будем решать, когда запускать НЛО? В оригинальной игре
тарелка перемещалась по экрану примерно каждые 25 секунд. Мы
сделаем то же самое в нашей игре внутри игрового цикла:

1

#запускайте блюдце каждые 20 секунд, если игра еще не окончена.

2
3
4
5

if game_over == False and game_time % 20000 == 0 and \
game_time > 0:
start_saucer()

Функция start_saucer инициализирует нашу тарелку и подготавливает
ее к перемещению по экрану. Она также воспроизводит особый звук
блюдца:

Space Invasion в PyGame

1
2
3
4
5
6

134

def start_saucer():
if saucer.active == False:
saucer.reset(window_width - 50, 50, level)
saucer.active = True
saucer_sound.play()
saucer.position = (window_width - 50, 50)

Когда тарелка инициализирована, нам нужно, чтобы она двигалась
по экрану. Мы делаем это через функцию handle_saucer_movement в
основном цикле.

1
2
3

def handle_saucer_movement():
global game_time
saucer_show_score_time = 1000

4
5
6
7
8
9
10
11
12
13
14

if saucer.active:
saucer.move_left()
saucer.update()
saucer.draw(window)
saucer.switch_image(
int(game_time/saucer_blink_speed) % 3)
(saucer_x, saucer_y) = saucer.position
if (saucer_x
saucer_show_score_time:
saucer.active = False

Эта функция отвечает за перемещение, обновление и
рисование тарелки (или НЛО) из игры, подобной Space Invaders.
Она также обрабатывает случай, когда тарелка «мертвая»
(например, попала под снаряд).

Space Invasion в PyGame

135

Вот пошаговое объяснение кода:
1.

2.
3.
4.
5.
6.

7.

8.
9.

saucer_show_score_time = 1000: эта строка устанавливает
переменную, представляющую время (в миллисекундах), в
течение которого на экране будет отображаться оценка
тарелки после ее уничтожения.
if saucer.active: Это условие проверяет, является ли
тарелка активной (видимой и движущейся на экране).
saucer.move_left(): если тарелка активна, этот метод
перемещает тарелку влево.
saucer.update():этот метод обновляет положение тарелки и
ее спрайты изображения.
saucer.draw(window): Тарелка рисуется на заданной
поверхности (окно).
saucer.switch_image(int(game_time/saucer_blink_speed)% 3): эта
строка переключает спрайт изображения тарелки в
зависимости от текущего игрового времени и переменной
под названием saucer_blink_speed, создавая эффект
анимации.
Следующие строки проверяют, не сдвинулась ли тарелка
с экрана (влево). Если это так,тарелка считается мертвой,
и её положение сбрасывается на (0, 0).
if saucer.dead: это условие проверяет, мертва ли тарелка.
current_saucer_death_time = pygame.time.get_ticks()
- saucer.death_time: эта строка вычисляет время, прошедшее с
момента убийства тарелки.

if current_saucer_death_time > saucer_show_score_time: это условие
проверяет, превышает ли время, прошедшее с момента смерти
блюдца, время, в течение которого должен отображаться счет
блюдца. Если true, блюдце становится неактивным (saucer.active =
False), то есть оно не будет отображаться или обновляться до тех
пор, пока не будет сброшено и активировано снова.

Space Invasion в PyGame

136

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

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

1
2

def handle_bullet(bullet, bullet_active):
...

3
4
5
6

if (handle_saucer_hit(bullet_x, bullet_y)):
bullet_active = False
bullet.position = (0, 0)

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

Space Invasion в PyGame

1
2
3
4
5
6
7
8
9
10
11
12
13

137

def handle_saucer_hit(bullet_x, bullet_y):
global player_score, bullet, saucer
(x, y) = saucer.position
# check if bullet collides with saucer
if bullet_x > x
and bullet_x < x + saucer.get_width()
and bullet_y > y
and bullet_y < y + saucer.get_height():
saucer.kill()
saucer_dying.play()
player_score += saucer.points
return True
return False

Заключение
В этой главе мы углубились в разработку динамичного 2D-шутера от
первого лица Space Invasion. Наше путешествие включало в себя
основные игровые компоненты, такие как пользовательский ввод,
звуковой дизайн, движение врагов, обнаружение столкновений и
захватывающая анимация.
Хотя Space Invasion немного сложнее по сравнению с нашими
предыдущими проектами, он служит отличным планом для
создания собственных игровых проектов в реальном времени.
Создавая уникальные спрайты, вы можете раскрыть свой
творческий потенциал, комбинируя и адаптируя их к множеству
игровых жанров и концепций. Это стремление не только расширяет
ваш набор навыков, но и открывает целый мир возможностей для
инноваций в игровой среде.

Приложение
Где найти изображения
В Open Game Art² можно выбрать бесплатные изображения из
Itch.io³, где есть как бесплатные, так и платные игровые активы.

Где найти звуки
На FreeSound.org⁴ есть множество бесплатных звуков, которые вы
можете загрузить для использования в своей игре.

¹https://github.com/microgold/pygames/blob/master/ReadMe.md
²https://opengameart.org/
³https://itch.io/
⁴https://freesound.org/

139

Appendix

Также на SoundBible.com⁵ есть бесплатные звуки, которые вы можете
просмотреть.

Другие источники
Python и Pygame могут быть сложными для изучения, но существует
множество отличных ресурсов, которые помогут вам начать работу.
Мы рекомендуем:

Для Python
Учебное пособие по Python — изучите программирование на Python
(шаг за шагом)⁶
Учебное пособие по Python — полный курс для начинающих⁷
Python.org⁸: официальный веб-сайт языка программирования Python
предоставляет множество информации для начинающих, включая
учебные пособия, документацию и ресурсы сообщества.
Codecademy⁹: Codecademy предлагает бесплатный курс Python для
начинающих.
Coursera¹⁰: Coursera предлагает бесплатные курсы Python от ведущих
университетов и институтов.
Learn Python the Hard Way¹¹: бесплатная онлайн-книга,
предлагающая практический подход к изучению Python, основанный
на упражнениях. []
Python для всех¹²: серия бесплатных онлайн-курсов, предлагаемых
Мичиганским университетом, которые знакомят с
программированием на Python.
⁵https://soundbible.com/
⁶https://www.youtube.com/watch?v=XGf2GcyHPhc
⁷https://www.youtube.com/watch?v=rfscVS0vtbw
⁸https://www.python.org/
⁹https://www.codecademy.com/learn/learn-python
¹⁰https://www.coursera.org/courses?query=python
¹¹https://learnpythonthehardway.org/python3/
¹²https://www.py4e.com/lessons

140

Appendix

W3Schools¹³: веб-сайт, который предлагает различные учебные
пособия и ресурсы для изучения Python, а также других языков
программирования и технологий веб-разработки.

More PyGames
Brick Breaker¹⁴
Sudoku¹⁵
Snake Game¹⁶

¹³https://www.w3schools.com/python/
¹⁴https://www.geeksforgeeks.org/brick-breaker-game-in-python-using-pygame/
¹⁵https://www.geeksforgeeks.org/building-and-visualizing-sudoku-game-using-pygame/
¹⁶https://www.edureka.co/blog/snake-game-with-pygame/