Игра на PyGame собирается из пяти частей: окно с игровым циклом, объект игрока в виде pygame.Rect, события клавиатуры и таймера, проверка столкновений через colliderect() и вывод текста шрифтом. В этой инструкции мы соберём из них законченную игру «Звездопад»: платформа внизу ловит падающие звёзды, три пропуска заканчивают раунд, клавиша R начинает заново. Весь код умещается в 82 строки, каждый фрагмент запущен на PyGame 2.6.1 и Python 3.13, в конце соберём игру в exe-файл. Если вы ещё выбираете, с какого движка начать, сперва загляните в наш гайд по созданию игр для новичков, а библиотеку поставьте по инструкции по установке PyGame.
Что мы делаем и что понадобится

«Звездопад» — игра на одно окно 800 × 600. Внизу ходит платформа шириной 80 пикселей, сверху каждые 0,7 секунды появляется звезда и падает вниз. Поймали — плюс один к счёту, упустили — плюс один к промахам. Три промаха, и игра останавливается с надписью «Игра окончена».
Такой проект закрывает всё, о чём спрашивают новички после первого окна: как двигать объект клавишами, как заставить что-то появляться само, как считать столкновения и как вывести счёт. Каждый следующий шаг добавляет 10–20 строк к предыдущему, поэтому файл можно запускать после любого раздела.
| Шаг | Что добавляем | Какой модуль PyGame работает |
|---|---|---|
| 1 | Окно, фон, игровой цикл, 60 кадров в секунду | display, time.Clock |
| 2 | Платформа игрока и управление стрелками | Rect, key.get_pressed(), draw.rect |
| 3 | Звёзды появляются по таймеру и падают | event.custom_type(), time.set_timer() |
| 4 | Столкновения, счёт и промахи | Rect.colliderect() |
| 5 | Текст на экране, конец игры и рестарт | font.SysFont, Surface.blit |
| 6 | Сборка в exe для друзей без Python | PyInstaller |
Перед стартом нужны три вещи: Python (готовые сборки PyGame 2.6.1 на PyPI есть для версий с 3.6 по 3.13), установленная библиотека и пустой файл game.py в редакторе. Как создать и запустить файл, мы разбирали в инструкции для VS Code. Проверьте установку одной строкой в терминале: python -c "import pygame; print(pygame.version.ver)". Если печатает 2.6.1 или похожий номер, можно начинать.
Проверьте версию Python. У PyGame 2.6.1 на PyPI есть готовые сборки только до Python 3.13. На Python 3.14
pipсоберёт библиотеку из исходников, и модуль шрифтов в такой сборке не работает: игра упадёт на шаге 5 с ошибкойfont module not available. Мы поймали её на своей машине. Решение: поставить Python 3.13 либо форкpygame-ce, у которого сборки под 3.14 уже есть.
Шаг 1: окно и игровой цикл
Первый файл открывает окно, красит его в тёмно-синий и ждёт, пока вы нажмёте крестик. Это тот же каркас, что и в нашей инструкции про первое окно в PyGame, только с константами наверху: размер и частоту кадров удобнее менять в одном месте.
import pygame
WIDTH, HEIGHT = 800, 600
FPS = 60
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Звездопад")
clock = pygame.time.Clock()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill((15, 15, 40))
pygame.display.flip()
clock.tick(FPS)
pygame.quit()
Три строки внутри цикла делают всю работу. pygame.event.get() забирает из очереди всё, что накопилось за кадр: нажатия, движения мыши, сигнал закрытия окна. Без этого вызова система решит, что программа зависла, и предложит её закрыть. screen.fill() стирает предыдущий кадр, pygame.display.flip() показывает новый.
clock.tick(FPS) ограничивает скорость цикла: по документации модуля time метод замеряет время с прошлого вызова и, если передать частоту, делает паузу, чтобы цикл не крутился чаще заданного числа раз в секунду. Без него игра на мощном ноутбуке побежит в десять раз быстрее, чем на слабом. Если сама конструкция while running вызывает вопросы, у нас есть отдельный разбор цикла while в Python.
Шаг 2: платформа игрока и управление
Игрока в «Звездопаде» изображает прямоугольник pygame.Rect. К этому классу вы будете обращаться чаще всего: он хранит координаты и размер, умеет двигаться, сравнивать себя с другими прямоугольниками и не выходить за рамки экрана. Добавьте константу PLAYER_SPEED = 7 к остальным, а цикл замените на этот:
player = pygame.Rect(WIDTH // 2 - 40, HEIGHT - 50, 80, 20)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] or keys[pygame.K_a]:
player.x -= PLAYER_SPEED
if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
player.x += PLAYER_SPEED
player.clamp_ip(screen.get_rect())
screen.fill((15, 15, 40))
pygame.draw.rect(screen, (90, 200, 255), player, border_radius=6)
pygame.display.flip()
clock.tick(FPS)
Для движения мы используем pygame.key.get_pressed(), а не событие KEYDOWN. Событие приходит один раз в момент нажатия, а get_pressed() возвращает состояние всех клавиш на текущий кадр. Пока стрелка зажата, платформа едет, отпустили — остановилась. Стрелки и клавиши A/D работают одинаково, так удобнее на ноутбуках. Если запись keys[pygame.K_LEFT] пока читается с трудом, значит, не хватает базы языка: индексы, словари и булевы значения проходят в первые недели любого курса по Python, и после них код игры перестанет выглядеть заклинанием.
player.clamp_ip(screen.get_rect()) прижимает прямоугольник к границам окна: если платформа вылезла за левый край, PyGame вернёт её обратно. Одна строка вместо двух проверок if. Параметр border_radius у draw.rect скругляет углы, на игровую логику он не влияет.
Шаг 3: звёзды падают по таймеру

Звёзды хранятся списком прямоугольников. Новую звезду рождает таймер: pygame.time.set_timer() кладёт в очередь событие с нужным номером каждые SPAWN_MS миллисекунд, а в цикле мы ловим его так же, как QUIT. Номер события выдаёт pygame.event.custom_type(); документация модуля event рекомендует именно этот способ вместо старого USEREVENT + 1, чтобы номера не пересекались с чужими.
import random # в начало файла, рядом с import pygame
STAR_SIZE = 24
STAR_SPEED = 4
SPAWN_MS = 700
stars = []
SPAWN_STAR = pygame.event.custom_type()
pygame.time.set_timer(SPAWN_STAR, SPAWN_MS)
# внутри цикла событий:
elif event.type == SPAWN_STAR:
x = random.randint(0, WIDTH - STAR_SIZE)
stars.append(pygame.Rect(x, -STAR_SIZE, STAR_SIZE, STAR_SIZE))
# после обработки клавиш, до отрисовки:
for star in stars:
star.y += STAR_SPEED
stars = [star for star in stars if star.top < HEIGHT]
# в отрисовке, после игрока:
for star in stars:
pygame.draw.rect(screen, (255, 220, 80), star, border_radius=8)
Звезда появляется чуть выше верхнего края, на y = -STAR_SIZE, и каждый кадр смещается вниз на STAR_SPEED пикселей. При 60 кадрах в секунду и скорости 4 пикселя путь до низа занимает около 2,6 секунды. Строка со списковым включением выбрасывает звёзды, которые улетели за экран, иначе список будет расти, пока игра запущена.
Первое событие таймера придёт только через 700 миллисекунд после запуска: по документации таймер не срабатывает мгновенно. Поэтому первые полсекунды экран пустой, и это нормально.
Шаг 4: столкновения, счёт и промахи
Столкновение двух прямоугольников проверяет Rect.colliderect(): метод возвращает True, если области пересекаются хотя бы одним пикселем. Звезду, которая коснулась платформы, удаляем из списка и добавляем очко. Звезду, которая ушла ниже экрана, тоже удаляем, но засчитываем промах.
MAX_MISSES = 3
score = 0
misses = 0
game_over = False
# вместо простого сдвига звёзд:
if not game_over:
for star in stars[:]:
star.y += STAR_SPEED
if star.colliderect(player):
stars.remove(star)
score += 1
elif star.top > HEIGHT:
stars.remove(star)
misses += 1
if misses >= MAX_MISSES:
game_over = True
Обратите внимание на stars[:] в заголовке цикла. Мы перебираем копию списка, а удаляем из оригинала. Если перебирать список и удалять из него же, Python пропустит каждый второй элемент: на тестовом списке [1, 2, 3, 4] после такого цикла остаются [2, 4]. В игре это выглядит как звезда, которая пролетела сквозь платформу. Если списки для вас пока новая тема, начните с короткой инструкции про сумму списка в Python: там те же циклы и включения на простом примере.
Флаг game_over замораживает мир: звёзды перестают двигаться и появляться, платформа не реагирует на клавиши. При этом отрисовка продолжается, поэтому игрок видит последний кадр с надписью, а не пустое окно.
Шаг 5: текст, конец игры и рестарт
Текст в PyGame выводится в два приёма: шрифт рисует строку на отдельную поверхность методом render(), а screen.blit() копирует её на экран в нужную точку. Шрифт создаём один раз после pygame.init(), иначе игра будет заново загружать его 60 раз в секунду. Выбор шрифта, вывод нескольких строк и кнопки с обработкой клика разобраны отдельно в инструкции про текст и кнопки в PyGame.
font = pygame.font.SysFont("arial", 32) # после pygame.init()
def draw_text(text, x, y, color=(255, 255, 255)):
surface = font.render(text, True, color)
screen.blit(surface, (x, y))
# в отрисовке, после звёзд:
draw_text(f"Счёт: {score}", 20, 15)
draw_text(f"Пропущено: {misses}/{MAX_MISSES}", 20, 55)
if game_over:
draw_text("Игра окончена. R — заново, Esc — выход", 130, HEIGHT // 2, (255, 120, 120))
# в цикле событий:
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
elif event.key == pygame.K_r and game_over:
player, stars, score, misses = new_game()
game_over = False
SysFont("arial", 32) ищет Arial в системе. Если такого шрифта нет, PyGame по документации модуля font подставит встроенный freesansbold, а он поддерживает кириллицу, так что «Счёт» и «Пропущено» отрисуются на любой машине. Второй аргумент render() включает сглаживание.
Рестарт делаем через функцию new_game(), которая возвращает свежую платформу, пустой список звёзд и нули счётчиков. Клавиша R работает только при game_over, чтобы случайное нажатие в разгар раунда не обнулило счёт. Esc закрывает окно в любой момент.
Полный код игры
Ниже файл целиком, 82 строки. Скопируйте его в game.py и запустите командой python game.py. Мы прогнали его тремя тестами: без движения платформы игра заканчивается ровно после третьего промаха, с платформой во всю ширину экрана за шесть секунд ловится четыре звезды, а клавиша R после конца раунда обнуляет счётчики. Привычка проверять код до того, как показать его другим, пригодится и дальше: в плане как стать Python-разработчиком тесты стоят отдельным пунктом.
import random
import pygame
WIDTH, HEIGHT = 800, 600
FPS = 60
PLAYER_SPEED = 7
STAR_SIZE = 24
STAR_SPEED = 4
SPAWN_MS = 700
MAX_MISSES = 3
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Звездопад")
clock = pygame.time.Clock()
font = pygame.font.SysFont("arial", 32)
SPAWN_STAR = pygame.event.custom_type()
pygame.time.set_timer(SPAWN_STAR, SPAWN_MS)
def new_game():
player = pygame.Rect(WIDTH // 2 - 40, HEIGHT - 50, 80, 20)
return player, [], 0, 0
def draw_text(text, x, y, color=(255, 255, 255)):
surface = font.render(text, True, color)
screen.blit(surface, (x, y))
player, stars, score, misses = new_game()
game_over = False
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
elif event.key == pygame.K_r and game_over:
player, stars, score, misses = new_game()
game_over = False
elif event.type == SPAWN_STAR and not game_over:
x = random.randint(0, WIDTH - STAR_SIZE)
stars.append(pygame.Rect(x, -STAR_SIZE, STAR_SIZE, STAR_SIZE))
if not game_over:
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] or keys[pygame.K_a]:
player.x -= PLAYER_SPEED
if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
player.x += PLAYER_SPEED
player.clamp_ip(screen.get_rect())
for star in stars[:]:
star.y += STAR_SPEED
if star.colliderect(player):
stars.remove(star)
score += 1
elif star.top > HEIGHT:
stars.remove(star)
misses += 1
if misses >= MAX_MISSES:
game_over = True
screen.fill((15, 15, 40))
pygame.draw.rect(screen, (90, 200, 255), player, border_radius=6)
for star in stars:
pygame.draw.rect(screen, (255, 220, 80), star, border_radius=8)
draw_text(f"Счёт: {score}", 20, 15)
draw_text(f"Пропущено: {misses}/{MAX_MISSES}", 20, 55)
if game_over:
draw_text("Игра окончена. R — заново, Esc — выход", 130, HEIGHT // 2, (255, 120, 120))
pygame.display.flip()
clock.tick(FPS)
pygame.quit()
Если хочется другого темпа, меняйте только константы наверху. STAR_SPEED = 6 сделает раунд заметно сложнее, SPAWN_MS = 400 удвоит плотность звёзд, MAX_MISSES = 5 даст новичку право на ошибку. Логику трогать не нужно. Такой файл уже можно класть в портфолио: какие проекты ждут от новичка и чем вообще занимается Python-разработчик, мы разбирали отдельно.
Как собрать игру в exe

Чтобы отдать игру человеку без Python, соберите её в один исполняемый файл через PyInstaller. Две команды в терминале из папки с game.py:
pip install pyinstaller
pyinstaller --onefile --windowed --name zvezdopad game.py
Готовый файл появится в папке dist. На нашей машине сборка заняла около семи секунд, файл получился размером 15 МБ: внутри лежат интерпретатор Python, PyGame и SDL. Флаги по документации PyInstaller:
| Флаг | Что делает |
|---|---|
--onefile |
Собирает всё в один файл вместо папки с сотней библиотек |
--windowed |
На Windows и macOS не открывает чёрное окно консоли рядом с игрой; на Linux флаг игнорируется |
--name |
Имя итогового файла; без флага возьмётся имя скрипта |
Собирать нужно на той системе, для которой делаете файл: PyInstaller на macOS выдаст приложение для macOS, а не exe для Windows. Если в игре появятся картинки и звуки, их придётся добавлять в сборку отдельно флагом --add-data, у «Звездопада» внешних файлов нет, поэтому хватает двух команд. Сборка, установщик и публикация в магазинах обычно идут отдельным модулем на курсах по разработке игр, там же разбирают, чем PyInstaller отличается от экспорта из Unity.
Что добавить, когда база работает
«Звездопад» намеренно сделан на прямоугольниках, чтобы весь код был виден целиком. Дальше игру развивают в четыре стороны:
- Картинки вместо прямоугольников.
pygame.image.load()загружает PNG, аget_rect()у полученной поверхности даёт тот жеRectдля столкновений. Логика шагов 3 и 4 не меняется. - Звук. Модуль
pygame.mixerпроигрывает WAV и OGG: один звук на пойманную звезду, другой на промах. - Нарастающая сложность. Каждые десять очков уменьшайте
SPAWN_MSчерез повторный вызовset_timer()с новым интервалом. - Меню и рекорд. Стартовый экран с кнопкой делается тем же
Rectи событиемMOUSEBUTTONDOWN, а рекорд сохраняется в текстовый файл.
Если после этого захочется классического проекта, следующая ступень — змейка на PyGame: там добавляется сетка поля и хвост, который растёт списком координат. А если вас больше тянет в индустрию, чем в библиотеку, посмотрите, сколько зарабатывает Python-разработчик: игровые студии берут питонистов на инструменты и серверную часть.
Частые ошибки и что с ними делать
Все ошибки из таблицы мы воспроизвели на PyGame 2.6.1, тексты сообщений приведены как есть.
| Симптом | Причина | Что сделать |
|---|---|---|
ModuleNotFoundError: No module named 'pygame' |
Библиотека стоит в другом интерпретаторе, чем тот, что запускает файл | Поставить командой python -m pip install pygame тем же python, которым запускаете игру; подробно в инструкции по установке |
pygame.error: video system not initialized |
Работа с окном или событиями до pygame.init() либо после pygame.quit() |
Проверить, что init() стоит до первого обращения к display и event, а после quit() ничего не рисуется |
| Окно «не отвечает», курсор крутится | В цикле нет pygame.event.get(), очередь событий переполняется |
Вызывать event.get() каждый кадр, даже если события не нужны |
| Окно чёрное, ничего не движется | Забыт pygame.display.flip() или он стоит до отрисовки |
Ставить flip() последним в кадре, после всех draw и blit |
| Объект не двигается при малой скорости | Координаты Rect целые: прибавка 0,3 округляется, десять сдвигов по 0,3 дают ноль |
Хранить позицию в переменной с плавающей точкой и записывать rect.x = round(pos_x) раз в кадр |
| Звезда пролетает сквозь платформу | Удаление из списка во время перебора этого же списка | Перебирать копию stars[:] или собирать новый список включением |
font module not available |
PyGame 2.6.1 собран из исходников под Python 3.14 без поддержки шрифтов | Перейти на Python 3.13 или поставить pygame-ce |
Где освоить Python и разработку игр системно
Игра из этой инструкции показывает механику PyGame, но не учит проектировать код: классы для объектов, состояния сцен, загрузку ресурсов. Это уже уровень курса, а не статьи. Если вы хотите делать игры, смотрите программы подготовки разработчиков игр, где PyGame идёт как первая ступень перед Unity или Unreal. Если вам нужен сам язык, ниже подборка курсов по Python с проектами в портфолио.
| Курс | Школа | Стоимость со скидкой | В рассрочку | Длительность | Обзор курса от Checkroi |
|---|---|---|---|---|---|
| Профессия «Python-разработчик» Перейти на сайт курса | 157 335 ₽ | 5987 ₽/мес. | 10 месяцев | Обзор курса | |
| Fullstack-разработчик на Python Перейти на сайт курса | 175 800 ₽ | 7125 ₽/мес. | 21 месяц | Обзор курса | |
| ДО Профессия Python-разработчик Перейти на сайт курса | 149 001 ₽ | 3167 ₽/мес. | 10 месяцев | Обзор курса | |
| Автоматизированное тестирование на Python Перейти на сайт курса | 118 666 ₽ | 4108 ₽/мес. | 9 месяцев | Обзор курса | |
| Программирование на Python ПРО Перейти на сайт курса | 119 600 ₽ | 250 000 ₽/мес. | 17 месяцев | Обзор курса |
Больше программ — в полном каталоге курсов по Python




