Pygame не входит в стандартную поставку Python, для установки библиотеки выполните
pip install pygame
Все возможности библиотеки Pygame нереально рассмотреть в одной статье, поэтому здесь мы затронем только самые базовые концепции – рисование, движение объектов, покадровую анимацию, обработку событий, обновление счетчика, обнаружение столкновения.
1 Окно и главный цикл приложения
Создание любого приложения на базе Pygame начинается с импорта и инициализации библиотеки. Затем нужно определить параметры окна, и по желанию – задать цвет (или изображение) фона:
import pygame
# инициализируем библиотеку Pygame
pygame.init()
# определяем размеры окна
window_size = (300, 300)
# задаем название окна
pygame.display.set_caption("Синий фон")
# создаем окно
screen = pygame.display.set_mode(window_size)
# задаем цвет фона
background_color = (0, 0, 255) # синий
# заполняем фон заданным цветом
screen.fill(background_color)
# обновляем экран для отображения изменений
pygame.display.flip()
# показываем окно, пока пользователь не нажмет кнопку "Закрыть"
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()

Цикл while True играет роль главного цикла программы – в нем происходит отслеживание событий приложения и действий пользователя. Функция pygame.quit() завершает работу приложения, и ее можно назвать противоположностью функции pygame.init(). Для завершения Python-процесса используется exit(), с той же целью можно использовать sys.exit(), но ее нужно импортировать в начале программы: import sys.
В качестве фона можно использовать изображение:
import pygame
pygame.init()
window_size = (400, 400)
screen = pygame.display.set_mode(window_size)
pygame.display.set_caption("Peter the Piglet")
# загружаем изображение
background_image = pygame.image.load("background.png")
# подгоняем масштаб под размер окна
background_image = pygame.transform.scale(background_image, window_size)
# накладываем изображение на поверхность
screen.blit(background_image, (0, 0))
pygame.display.flip()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()

Обработку событий (нажатий клавиш и кликов) в Pygame реализовать очень просто – благодаря встроенным функциям. Приведенный ниже код изменяет цвет фона после клика по кнопке. Обратите внимание, что в Pygame можно задавать цвет несколькими способами:
import pygame
pygame.init()
pygame.display.set_caption('Измени цвет фона')
window_surface = pygame.display.set_mode((300, 300))
background = pygame.Surface((300, 300))
background.fill(pygame.Color('#000000'))
color_list = [
pygame.Color('#FF0000'), # красный
pygame.Color('#00FF00'), # зеленый
pygame.Color('#0000FF'), # синий
pygame.Color('#FFFF00'), # желтый
pygame.Color('#00FFFF'), # бирюзовый
pygame.Color('#FF00FF'), # пурпурный
pygame.Color('#FFFFFF') # белый
]
current_color_index = 0
button_font = pygame.font.SysFont('Verdana', 15) # используем шрифт Verdana
button_text_color = pygame.Color("black")
button_color = pygame.Color("gray")
button_rect = pygame.Rect(100, 115, 100, 50)
button_text = button_font.render('Нажми!', True, button_text_color)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
if button_rect.collidepoint(event.pos):
current_color_index = (current_color_index + 1) % len(color_list)
background.fill(color_list[current_color_index])
window_surface.blit(background, (0, 0))
pygame.draw.rect(window_surface, button_color, button_rect)
button_rect_center = button_text.get_rect(center=button_rect.center)
window_surface.blit(button_text, button_rect_center)
pygame.display.update()

Как очевидно из приведенного выше примера, основной цикл Pygame приложения состоит из трех повторяющихся действий:
- Обработка событий (нажатий клавиш или кнопок).
- Обновление состояния.
- Отрисовка состояния на экране.
2 GUI для PyGame
Pygame позволяет легко и быстро интегрировать в проект многие нужные вещи – шрифты, звук, обработку событий, – однако не имеет встроенных виджетов для создания кнопок, лейблов, индикаторов выполнения и других подобных элементов интерфейса. Эту проблему разработчик должен решать либо самостоятельно (нарисовать прямоугольник, назначить ему функцию кнопки), либо с помощью дополнительных GUI-библиотек. Таких библиотек несколько, к самым популярным относятся:
- Pygame GUI
- Thorpy
- PGU
Вот простой пример использования Pygame GUI – зеленые нули и единицы падают вниз в стиле «Матрицы»:
import pygame
import pygame_gui
import random
window_size = (800, 600)
window = pygame.display.set_mode(window_size)
pygame.display.set_caption('Матрица Lite')
pygame.init()
gui_manager = pygame_gui.UIManager(window_size)
font = pygame.font.SysFont('Consolas', 20)
text_color = pygame.Color('green')
text_symbols = ['0', '1']
text_pos = [(random.randint(0, window_size[0]), 0) for i in range(50)]
text_speed = [(0, random.randint(1, 5)) for i in range(50)]
text_surface_list = []
button_size = (100, 50)
button_pos = (350, 250)
button_text = 'Матрица!'
button = pygame_gui.elements.UIButton(
relative_rect=pygame.Rect(button_pos, button_size),
text=button_text,
manager=gui_manager
)
while True:
time_delta = pygame.time.Clock().tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame_gui.UI_BUTTON_PRESSED:
text_surface_list = []
for i in range(50):
text_symbol = random.choice(text_symbols)
text_surface = font.render(text_symbol, True, text_color)
text_surface_list.append(text_surface)
gui_manager.process_events(event)
gui_manager.update(time_delta)
window.fill(pygame.Color('black'))
for i in range(50):
text_pos[i] = (text_pos[i][0], text_pos[i][1] + text_speed[i][1])
if text_pos[i][1] > window_size[1]:
text_pos[i] = (random.randint(0, window_size[0]), -20)
if len(text_surface_list) > i:
window.blit(text_surface_list[i], text_pos[i])
gui_manager.draw_ui(window)
pygame.display.update()

3 Анимация и обработка событий
Выше, в примере с падающими символами в «Матрице», уже был показан принцип простейшей имитации движения, который заключается в последовательном изменении координат объекта и обновлении экрана с установленной частотой кадра pygame.time.Clock().tick(60). Усложним задачу – сделаем простую анимацию с падающими розовыми «звездами». Приложение будет поддерживать обработку двух событий:
- При клике мышью по экрану анимация останавливается.
- При нажатии клавиши Enter – возобновляется.
- Кроме того, добавим счетчик упавших звезд. Готовый код выглядит так:
import pygame
import random
pygame.init()
screen_width = 640
screen_height = 480
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Звезды падают вниз")
black = (0, 0, 0)
white = (255, 255, 255)
pink = (255, 192, 203)
font = pygame.font.SysFont("Verdana", 15)
star_list = []
for i in range(50):
x = random.randrange(screen_width)
y = random.randrange(-200, -50)
speed = random.randrange(1, 5)
star_list.append([x, y, speed])
score = 0
freeze = False # флаг для определения момента остановки
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.MOUSEBUTTONDOWN: # останавливаем падение звезд по клику
freeze = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RETURN: # возобновляем движение вниз, если нажат Enter
freeze = False
if not freeze: # если флаг не активен,
# звезды падают вниз
for star in star_list:
star[1] += star[2]
if star[1] > screen_height:
star[0] = random.randrange(screen_width)
star[1] = random.randrange(-200, -50)
score += 1
# рисуем звезды, выводим результаты подсчета
screen.fill(black)
for star in star_list:
pygame.draw.circle(screen, pink, (star[0], star[1]), 3)
score_text = font.render("Упало звезд: " + str(score), True, white)
screen.blit(score_text, (10, 10))
pygame.display.update()
# устанавливаем частоту обновления экрана
pygame.time.Clock().tick(60)

4 Столкновение объектов
В этом примере расстояние между объектами проверяется до тех пор, пока объекты не столкнутся. В момент столкновение движение прекращается, а цвет объектов – изменяется:
import pygame
import math
pygame.init()
screen_width = 400
screen_height = 480
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Драматическое столкновение")
# размеры и позиция окружности
circle_pos = [screen_width/2, 50]
circle_radius = 20
# размеры и позиция прямоугольника
rect_pos = [screen_width/2, screen_height-50]
rect_width = 100
rect_height = 50
# цвета окружности и прямоугольника
white = (255, 255, 255)
black = (0, 0, 0)
green = (0, 255, 0)
red = (255, 0, 0)
# скорость движения окружности
speed = 5
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
# окружность движется вниз
circle_pos[1] += speed
# проверяем (используя формулу расстояния),
# столкнулась ли окружность с прямоугольником
circle_x = circle_pos[0]
circle_y = circle_pos[1]
rect_x = rect_pos[0]
rect_y = rect_pos[1]
distance_x = abs(circle_x - rect_x)
distance_y = abs(circle_y - rect_y)
if distance_x <= (rect_width/2 + circle_radius) and distance_y <= (rect_height/2 + circle_radius):
circle_color = red # изменяем цвет фигур
rect_color = green # в момент столкновения
else:
circle_color = green
rect_color = black
# рисуем окружность и прямоугольник на экране
screen.fill(white)
pygame.draw.circle(screen, circle_color, circle_pos, circle_radius)
pygame.draw.rect(screen, rect_color, (rect_pos[0]-rect_width/2, rect_pos[1]-rect_height/2, rect_width, rect_height))
pygame.display.update()
# останавливаем движение окружности, если она
# столкнулась с прямоугольником
if circle_pos[1] + circle_radius >= rect_pos[1] - rect_height/2:
speed = 0
# задаем частоту обновления экрана
pygame.time.Clock().tick(60)

5 Управление движением объекта
Для управления движением (в нашем случае – с помощью клавиш ← и →) используются pygame.K_RIGHT (вправо) и pygame.K_LEFT (влево). В приведенном ниже примере передвижение падающих окружностей возможно только до момента приземления на дно игрового поля, или на предыдущие фигуры. Для упрощения вычисления факта столкновения фигур окружности вписываются в прямоугольники:
import pygame
import random
pygame.init()
screen_width = 640
screen_height = 480
screen = pygame.display.set_mode((screen_width, screen_height))
# цвета окружностей
white = (255, 255, 255)
red = (255, 0, 0)
green = (0, 255, 0)
blue = (0, 0, 255)
black = (0, 0, 0)
yellow = (255, 255, 0)
# цвет, скорость, начальная позиция окружности
circle_radius = 30
circle_speed = 3
circle_color = random.choice([red, green, blue, yellow, white])
circle_pos = [screen_width//2, -circle_radius]
circle_landed = False
# список приземлившихся окружностей и их позиций
landed_circles = []
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
# если окружность не приземлилась
if not circle_landed:
# меняем направление по нажатию клавиши
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
circle_pos[0] -= circle_speed
if keys[pygame.K_RIGHT]:
circle_pos[0] += circle_speed
# проверяем, столкнулась ли окружность с другой приземлившейся окружностью
for landed_circle in landed_circles:
landed_rect = pygame.Rect(landed_circle[0]-circle_radius, landed_circle[1]-circle_radius, circle_radius*2, circle_radius*2)
falling_rect = pygame.Rect(circle_pos[0]-circle_radius, circle_pos[1]-circle_radius, circle_radius*2, circle_radius*2)
if landed_rect.colliderect(falling_rect):
circle_landed = True
collision_x = circle_pos[0]
collision_y = landed_circle[1] - circle_radius*2
landed_circles.append((collision_x, collision_y, circle_color))
break
# если окружность не столкнулась с другой приземлившейся окружностью
if not circle_landed:
# окружность движется вниз
circle_pos[1] += circle_speed
# проверяем, достигла ли окружность дна
if circle_pos[1] + circle_radius > screen_height:
circle_pos[1] = screen_height - circle_radius
circle_landed = True
# добавляем окружность и ее позицию в список приземлившихся окружностей
landed_circles.append((circle_pos[0], circle_pos[1], circle_color))
if circle_landed:
# если окружность приземлилась, задаем параметры новой
circle_pos = [screen_width//2, -circle_radius]
circle_color = random.choice([red, green, blue, yellow, white])
circle_landed = False
# рисуем окружности
screen.fill(black)
for landed_circle in landed_circles:
pygame.draw.circle(screen, landed_circle[2], (landed_circle[0], landed_circle[1]), circle_radius)
pygame.draw.circle(screen, circle_color, circle_pos, circle_radius)
pygame.display.update()
# частота обновления экрана
pygame.time.Clock().tick(60)

6 Доп пример. Лабиринт 2D наивная реализация
Pygame игра, которая генерирует лабиринт из вертикальных стен со случайным количеством дверей. Игрок (красная окружность) должен проходить через двери.
import pygame
import random
pygame.init()
screen_width = 640
screen_height = 480
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption('Лабиринт')
black = (0,0,0)
white = (255,255,255)
red = (255,0,0)
blue = (0,0,255)
green = (0,255,0)
# параметры стен и дверей
line_width = 10
line_gap = 40
line_offset = 20
door_width = 20
door_gap = 40
max_openings_per_line = 5
# параметры и стартовая позиция игрока
player_radius = 10
player_speed = 5
player_x = screen_width - 12
player_y = screen_height - line_offset
# рисуем стены и двери
lines = []
for i in range(0, screen_width, line_gap):
rect = pygame.Rect(i, 0, line_width, screen_height)
num_openings = random.randint(1, max_openings_per_line)
if num_openings == 1:
# одна дверь посередине стены
door_pos = random.randint(line_offset + door_width, screen_height - line_offset - door_width)
lines.append(pygame.Rect(i, 0, line_width, door_pos - door_width))
lines.append(pygame.Rect(i, door_pos + door_width, line_width, screen_height - door_pos - door_width))
else:
# несколько дверей
opening_positions = [0] + sorted([random.randint(line_offset + door_width, screen_height - line_offset - door_width) for _ in range(num_openings-1)]) + [screen_height]
for j in range(num_openings):
lines.append(pygame.Rect(i, opening_positions[j], line_width, opening_positions[j+1]-opening_positions[j]-door_width))
clock = pygame.time.Clock()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
# передвижение игрока
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and player_x > player_radius:
player_x -= player_speed
elif keys[pygame.K_RIGHT] and player_x < screen_width - player_radius:
player_x += player_speed
elif keys[pygame.K_UP] and player_y > player_radius:
player_y -= player_speed
elif keys[pygame.K_DOWN] and player_y < screen_height - player_radius:
player_y += player_speed
# проверка столкновений игрока со стенами
player_rect = pygame.Rect(player_x - player_radius, player_y - player_radius, player_radius * 2, player_radius * 2)
for line in lines:
if line.colliderect(player_rect):
# в случае столкновения возвращаем игрока назад
if player_x > line.left and player_x < line.right:
if player_y < line.top:
player_y = line.top - player_radius
else:
player_y = line.bottom + player_radius
elif player_y > line.top and player_y < line.bottom:
if player_x < line.left:
player_x = line.left - player_radius
else:
player_x = line.right + player_radius
screen.fill(black)
for line in lines:
pygame.draw.rect(screen, green, line)
pygame.draw.circle(screen, red, (player_x, player_y), player_radius)
pygame.display.update()
clock.tick(60)
