Как сделать игру на PyGame с нуля: пошагово до готовой игры и exe

Собираем законченную игру «Звездопад» на PyGame за шесть шагов: окно и игровой цикл, платформа игрока, звёзды по таймеру, столкновения, счёт и рестарт. Полный код на 82 строки, проверенный на PyGame 2.6.1, семь частых ошибок с реальными сообщениями и сборка в exe через PyInstaller.
Статью написал:
Ваня Буявец, продюсер, основатель Checkroi
Ваня Буявец
Основатель Checkroi, продюсер, эксперт в выборе онлайн-курсов
Все 2560 статей автора Подписаться на Телеграм-канал
Одобрено экспертом:
Наташа Буявец, основатель Checkroi, эксперт по онлайн-курсам
Наташа Буявец
Основательница Checkroi, продюсер Youtube-каналов, эксперт по онлайн-курсам
Все 3218 экспертных мнений Подписаться на Телеграм-канал
Обложка: Как сделать игру на PyGame с нуля: пошагово до готовой игры и exe

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

CheckroiCheckroiПодборка курсов по Python560 курсов • 46 школСравните цены, школы, программу и найдите выгодные предложения по обучениюСравнить

Что мы делаем и что понадобится

Рой делает первую игру на 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.

Канал основателя Checkroi Вани Буявца3 700 человек читают мой Телеграм-канал про нейросетиСобрал промпты для Claude Code и ChatGPT, разборы ИИ-инструментов и лайфхаки по продвижению бизнеса в одном местеПрисоединиться

Шаг 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 закрывает окно в любой момент.

Ваня БуявецКанал основателя Checkroi Вани БуявцаЗабирайте промпты и обучение по нейросетям в моём Телеграм-каналеБольше 3 700 человек уже применяют Claude Code, ChatGPT и другие нейросети в работе, учёбе, бизнесе и жизниПерейти в канал

Полный код игры

Ниже файл целиком, 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-разработчик»
Перейти на сайт курса
SkillboxSkillbox157 335 ₽5987 ₽/мес.10 месяцевОбзор курса
Fullstack-разработчик на Python
Перейти на сайт курса
НетологияНетология175 800 ₽7125 ₽/мес.21 месяцОбзор курса
ДО Профессия Python-разработчик
Перейти на сайт курса
GeekBrainsGeekBrains149 001 ₽3167 ₽/мес.10 месяцевОбзор курса
Автоматизированное тестирование на Python
Перейти на сайт курса
SkillboxSkillbox118 666 ₽4108 ₽/мес.9 месяцевОбзор курса
Программирование на Python ПРО
Перейти на сайт курса
SkillboxSkillbox119 600 ₽250 000 ₽/мес.17 месяцевОбзор курса

Больше программ — в полном каталоге курсов по Python

Часто задаваемые вопросы

Сколько строк кода нужно, чтобы сделать простую игру на PyGame?

Игре «Звездопад» из этой инструкции хватило 82 строк: окно и цикл, платформа игрока, звёзды по таймеру, столкновения, счёт и рестарт. Каждый шаг добавляет 10–20 строк, поэтому файл можно запускать после любого раздела.

Почему игра на PyGame работает с разной скоростью на разных компьютерах?

Без ограничения частоты кадров цикл крутится так быстро, как позволяет процессор. Вызов clock.tick(60) в конце каждого кадра делает паузу, чтобы цикл не выполнялся чаще 60 раз в секунду, и скорость объектов становится одинаковой на любой машине.

Как сделать, чтобы объекты в PyGame появлялись сами через равные промежутки?

Через таймер событий: pygame.time.set_timer(SPAWN_STAR, 700) кладёт событие с номером SPAWN_STAR в очередь каждые 700 миллисекунд. В цикле событий вы ловите его так же, как pygame.QUIT, и создаёте новый объект. Номер события лучше брать из pygame.event.custom_type().

Как проверить столкновение двух объектов в PyGame?

Если оба объекта описаны прямоугольниками pygame.Rect, достаточно a.colliderect(b): метод возвращает True при пересечении хотя бы в одном пикселе. У картинок прямоугольник даёт метод get_rect() загруженной поверхности.

Как вывести русский текст в окне PyGame?

Создайте шрифт один раз после pygame.init(), например pygame.font.SysFont("arial", 32), затем в кадре вызовите font.render("Счёт: 5", True, (255, 255, 255)) и скопируйте результат на экран через screen.blit(). Встроенный шрифт PyGame тоже поддерживает кириллицу, так что текст отрисуется даже без Arial.

Как превратить игру на PyGame в exe-файл?

Установите PyInstaller командой pip install pyinstaller и выполните pyinstaller --onefile --windowed game.py из папки с игрой. Готовый файл появится в папке dist. Собирать нужно на той же системе, для которой делаете файл: exe для Windows получается только на Windows.

Читайте Checkroi первым в Google
Добавьте Checkroi в избранные источники — и наши разборы курсов и обзоры школ будут показываться выше в вашей выдаче Google.
Оставить комментарий
0 комментариев
Форма комментария

Оставьте комментарий

Напишите, что думаете. Нам важно ваше мнение!