Для Python есть много библиотек, с помощью которых можно создавать графические интерфейсы. Сегодня поговорим об одной особенной — pygame. Это не просто визуальная оболочка, а полноценный инструмент для разработки игр. Если вы давно хотели написать игру, но пока не накопили денег на собственную студию — берите.
Если вы пока не работали с Python, но интересуетесь им, почитайте наш мастрид об этом языке программирования.
Что даёт pygame
Pygame подходит для того, чтобы понять, как вообще делаются игры: изучить устройство игрового кода, связь разных систем и общий процесс.
Вот две ключевые возможности библиотеки pygame, на которых мы будем строить всё остальное:
- Создание окна игры, в котором можно показывать что угодно. Без этого для работы у нас будет только код, а там сложно что-то нарисовать.
- Поиск входных действий игрока для правильного отображения кадров. Например, pygame проверяет положение курсора мыши или нажатие кнопок клавиатуры. Обычно Python тоже умеет принимать ввод через оператор
input()
, но на время ввода выполнение остального кода замораживается.
Эти два пункта — главные, но ещё pygame делает многие другие вещи удобнее: можно подключать звуки, находить коллизии, добавлять таймеры.
Крутые современные игры на одной pygame не сделаешь, потому что их создают при помощи множества других инструментов. У таких игр свой движок, редактор уровней, обработка освещения, саунд-дизайн и много других разных технологий под отдельные задачи. А ещё большая команда, которая всем этим занимается.
Устанавливаем и подключаем pygame
Для установки библиотеки используем команду:
pip install pygame
Если пишете код в текстовом редакторе, нужно зайти в командную строку, открыть нужную папку и выполнить команду:
Если работаете в IDE, то есть редакторе кода, команду можно выполнить во встроенном терминале:
Теперь нужно подключить библиотеку и принудительно включить все модули, потому что некоторые из них по умолчанию выключены. Это можно сделать с помощью такого кода:
# импортируем библиотеку pygame
import pygame
# инициализируем все модули pygame
pygame.init()
Если после этого запустить код, в консоли вывода должно появиться такое сообщение:
Hello from the pygame community. https://www.pygame.org/contribute.html
Создаём экран
Все игровые действия происходят в окне игры, или display surface. Это будет наш главный экран. Для его создания нужно объявить переменную и положить в неё два метода: display
и set_mode()
. Внутрь set_mode()
можно положить кортеж с шириной и высотой экрана. Но, чтобы было понятнее, положим их сначала в переменные:
# объявляем ширину и высоту экрана
width = 800
height = 400
# создаём экран игры
screen = pygame.display.set_mode((width, height))
Можно не использовать переменные и написать так: pygame.display.set_mode((800, 400))
. Но если в будущем нам понадобятся границы экрана где-то в другом месте кода, нужно будет продублировать эти значения. А при изменении менять во всех местах, где они указаны. С переменными удобнее — мы просто будем везде использовать width и height. Если будет нужно поменять эти значения, напишем новые в одном месте — там, где объявили переменные.
Сейчас можно попробовать запустить код, и на один кадр появится экран игры. Это очень мало, поэтому, скорее всего, вы ничего не заметите. Так происходит потому, что Python доходит до конца кода и перестаёт его выполнять — поэтому и окно тоже исчезает.
Сделаем так, чтобы код выполнялся бесконечно. Для этого используем бесконечный цикл и сразу добавим возможность выхода из него по нажатию кнопки закрытия окна:
# объявляем переменную-флаг для цикла игры
game = True
# запускаем бесконечный цикл
while game:
# получаем список возможных действий игрока
for event in pygame.event.get():
# если пользователь нажал на крестик закрытия окна…
if event.type == pygame.QUIT:
# …останавливаем цикл
pygame.quit()
# обновляем экран игры
pygame.display.update()
Теперь можно запустить код и посмотреть на готовый экран. Пока на нём ничего нет:
При выходе Python выдаёт ошибку. Так происходит потому, что в момент закрытия окна мы перестаём создавать экран. И когда программа доходит до последней строчки кода pygame.display.update()
, то не может её выполнить, отсюда и ошибка.
Мы будем писать чистый код, чтобы в будущем ошибки не накопились и не вызвали какую-то непонятную поломку. Поэтому сразу после добавления pygame мы подключим встроенный модуль, который позволяет корректно завершить любой запущенный код:
from sys import exit
Осталось описать правильное завершение работы в том же фрагменте кода, где мы уже прописали закрытие окна, — просто добавляем ещё одну строку exit()
:
# запускаем бесконечный цикл
while game:
# получаем список возможных действий игрока
for event in pygame.event.get():
# если пользователь нажал на крестик закрытия окна…
if event.type == pygame.QUIT:
# …останавливаем цикл
pygame.quit()
# добавляем корректное завершение работы
exit()
Теперь окно будет закрываться без ошибок.
Задаём название игры и частоту кадров
В нашей игре пока что ничего не происходит, но её уже можно запустить. Чтобы у запущенного окна появилось название, добавляем такую строку до основного цикла игры:
# даём название окну игры
pygame.display.set_caption("Detective CODE Game")
Теперь добавим ещё один важный параметр — частоту кадров в секунду, или fps (frame per second). Это то, как быстро сменяются кадры в нашей игре. Если в кино частота кадров обычно одинаковая (24 кадра в секунду), то для игры этот параметр может различаться в зависимости от мощности компьютера или конкретной сцены.
Если запустить на старом слабом компьютере современную игру последнего поколения на высших настройках, то в лучшем случае мы получим частоту 1–2 fps. Возможно и обратное: если запустить старую игру на мощном компьютере, частота обновления кадров может доходить до нескольких сотен в секунду (и иногда в это тоже невозможно играть).
Оба варианта вызовут проблемы при анимации. Допустим, по нашей задумке персонаж игры меняет положение в каждом новом кадре на 10 пикселей. Тогда получится так:
- при скорости 1 fps персонаж перемещается на 10 пикселей в секунду;
- при скорости 100 fps персонаж перемещается на 1000 пикселей в секунду.
Ограничить потолок скорости просто: нужно просто сказать компьютеру не работать быстрее заданного числа fps. Но если компьютер слабый и не тянет больше 10–15 кадров в секунду, его нельзя заставить отображать анимацию быстрее. Поэтому для игр указывают системные требования для запуска и нормальной работы.
Сегодня стандартная игровая частота кадров — 60 fps, так что мы зададим это число как максимальный показатель. Для этого понадобится добавить несколько строк кода до основного цикла:
# устанавливаем количество кадров в секунду
fps = 60
# создаём объект таймера
clock = pygame.time.Clock()
Обратите внимание, что метод Clock()
пишется с большой буквы — это важно.
Теперь в самом конце цикла добавляем такое:
# добавляем к таймеру количество fps для частоты обновления основного цикла
clock.tick(fps)
Наша игра запускается, у неё есть название и частота обновления кадров. Но пока что всё, что в ней есть, — чёрный экран. Добавим немного цвета.
Добавляем изображения
Все видимые объекты в pygame называются поверхностями. Их два вида:
- Основная, или display surface. Это наше окно игры. Основная поверхность может быть только одна, и она всегда видима.
- Рабочие поверхности, regular surface или просто surface. Это отдельные изображения, которые мы показываем на главном экране. Их может быть сколько угодно, и они видимы только при размещении на основной поверхности.
Создать рабочую поверхность можно двумя способами: нарисовать в pygame или загрузить заранее подготовленную картинку. Попробуем оба способа.
Чтобы нарисовать новую прямоугольную поверхность, нужно задать её размеры, сохранить в новую переменную с этими размерами и залить цветом. Для этого пишем до запуска цикла:
# создаём новую поверхность
# 1 — задаём размеры:
width_ts = 200
height_ts = 200
# 2 — создаём поверхность по размерам
test_surface = pygame.Surface((width_ts, height_ts))
# 3 — добавляем цвет
test_surface.fill('White')
Теперь нужно разместить новую поверхность на основной. Необязательно использовать главный экран, можно нарисовать одну дополнительную поверхность на другой (её можно назвать родительской). Для этого у родительской поверхности вызываем метод blit()
, в который передаём переменную с нужной поверхностью и координаты её левого верхнего угла.
На координатах остановимся подробнее. Координатная сетка в pygame совпадает с экраном игры и измеряется в пикселях. Начало находится в левом верхнем углу. Первая координата — горизонтальная, она увеличивается с перемещением вправо. Вторая координата отвечает за точку по вертикали и увеличивается сверху вниз.
Если мы рисуем одно изображение на другом, когда они оба не главные, то координатами нужно считать не основной экран, а родительскую поверхность. Выставляем для новой поверхности размещение в начале координат:
# размещаем новую поверхность на нашем экране
screen.blit(test_surface, (0, 0))
Запускаем:
Переместим белый квадрат на середину экрана:
# размещаем новую поверхность на нашем экране
screen.blit(test_surface, (300, 100))
Мы сдвинули левый верхний угол (начало новой поверхности) на 300 пикселей влево и на 100 вниз. Проверяем, что получилось:
Теперь попробуем второй способ создания изображения и сделаем фон. Сначала подготовим картинку одного размера с игровым окном и положим её в папку с основным скриптом. Теперь картинку можно сохранить в переменную через метод load()
, если в скобках указать имя файла:
# загружаем в переменную картинку из папки с нашим файлом
back = pygame.image.load('code_game_back.jpg')
Размещаем её на основном экране так же, как мы сделали это с предыдущим изображением:
# размещаем новую поверхность на нашем экране
screen.blit(back, (0, 0))
Смотрим на результат:
Обратите внимание, что нам даже не нужно удалять строку кода, добавляющую белый квадрат. Всё потому, что код картинки находится ниже. При выполнении кода pygame сначала нарисовала белый квадрат, а поверх него — картинку. Помните про построчный порядок исполнения кода, когда будете делать композицию из нескольких объектов.
Что дальше
У нас уже есть игра, которая запускается и показывает главное окно игры с одной картинкой.
В следующий раз ещё поработаем с изображениями, добавим образ главного героя и попробуем его анимировать.
import pygame
from sys import exit
pygame.init()
# объявляем ширину и высоту экрана
width = 800
height = 400
# создаём экран игры
screen = pygame.display.set_mode((width, height))
# устанавливаем количество кадров в секунду
fps = 60
# создаём объект таймера
clock = pygame.time.Clock()
# создаём новую поверхность
# 1 — задаём размеры:
width_ts = 200
height_ts = 200
# 2 — создаём поверхность по размерам
test_surface = pygame.Surface((width_ts, height_ts))
# 3 — добавляем цвет
test_surface.fill('White')
# загружаем в переменную картинку из папки с нашим файлом
back = pygame.image.load('code_game_back.jpg')
# даём название окну игры
pygame.display.set_caption("Detective CODE Game")
# объявляем переменную-флаг для цикла игры
game = True
# запускаем бесконечный цикл
while game:
# получаем список возможных действий игрока
for event in pygame.event.get():
# если пользователь нажал на крестик закрытия окна…
if event.type == pygame.QUIT:
# …останавливаем цикл
pygame.quit()
# добавляем корректное завершение работы
exit()
# размещаем новую поверхность на нашем экране — белый квадрат
screen.blit(test_surface, (300, 100))
# размещаем новую поверхность на нашем экране — подготовленный jpeg
screen.blit(back, (0, 0))
# обновляем экран игры
pygame.display.update()
# добавляем к таймеру количество fps для частоты обновления основного цикла
clock.tick(fps)