Учебный курс по разработке 2D runner-игры на Pygame.
В рамках курса студенты постепенно создают небольшую игру: персонаж бежит по уровню, прыгает, собирает монеты, избегает препятствий, набирает очки, проигрывает при столкновении и может начать новую попытку.
Курс показывает не только базовые возможности Pygame, но и постепенное развитие проекта: от одного простого файла до более аккуратной структуры с модулями, классами, менеджерами объектов, интерфейсом и отдельным состоянием игровой сессии.
| # | Лекция | Тема |
|---|---|---|
| 0 | Подготовка окружения | uv, Python 3.13, установка Pygame |
| 1 | Игровой цикл | Окно, события, FPS, первая отрисовка |
| 2 | Изображения и ресурсы | Surface, Rect, blit, папка assets |
| 3 | Анимация и runner-механика | Анимация персонажа, движущийся фон, прыжок |
| 4 | Препятствия и монеты | Столкновения, счёт, Game Over |
| 5 | Архитектура проекта | Модули, классы, разделение ответственности |
| 6 | Меню и состояния игры | menu, playing, pause, game_over |
| 7 | Звуки и музыка | pygame.mixer, эффекты, фоновая музыка |
| 8 | Управляемый спавн | SpawnManager, случайные промежутки между объектами |
| 9 | Менеджеры объектов | BaseObjectManager, CoinManager, ObstacleManager |
| 10 | Интерфейс | UIManager, меню, счёт, пауза, Game Over |
| 11 | Игровая сессия | GameSession, счёт, лучший результат, скорость, состояние |
| 12 | Финальная сборка | Чистка проекта, итоговая структура, проверка игры |
- Python 3.13
- uv — управление окружением и зависимостями
- Pygame — библиотека для разработки 2D-игры
- Ruff — линтер и форматтер
- pre-commit — локальные проверки перед коммитом
- GitHub Actions — CI-проверки на сервере
git clone git@github.com:agkondakov91/PyGame.git
cd PyGameuv --versionЕсли uv не установлен:
# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows PowerShell
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"uv python install 3.13В проекте версия Python закреплена в файле .python-version:
3.13
uv sync --devЭта команда установит основные зависимости проекта и dev-инструменты.
uv run python main.py| Действие | Клавиши |
|---|---|
| Запустить игру из меню | Enter или Space |
| Движение влево | A или ← |
| Движение вправо | D или → |
| Прыжок | W, ↑ или Space |
| Пауза | P |
| Продолжить после паузы | P |
| Рестарт после Game Over | R |
| Возврат в меню после Game Over | Esc |
.
├── .github/
│ └── workflows/
│ └── lint.yml # CI-проверки через GitHub Actions
│
├── assets/
│ ├── images/
│ │ ├── background.png # фон игры
│ │ ├── coin.png # монета
│ │ ├── obstacle.png # препятствие
│ │ ├── player_right_1.png # кадры анимации игрока
│ │ ├── player_right_2.png
│ │ ├── player_right_3.png
│ │ └── player_right_4.png
│ │
│ └── sounds/
│ ├── coin.wav # звук сбора монеты
│ ├── hit.wav # звук столкновения
│ ├── jump.wav # звук прыжка
│ └── music.mp3 # фоновая музыка
│
├── src/
│ └── runner/
│ ├── __init__.py
│ ├── assets.py # пути и загрузка ресурсов
│ ├── background.py # движущийся фон
│ ├── coin.py # объект монеты
│ ├── constants.py # константы проекта
│ ├── game.py # главный координатор игры
│ ├── obstacle.py # объект препятствия
│ ├── player.py # игрок, движение, прыжок, анимация
│ ├── session.py # состояние игровой сессии
│ ├── sound.py # звуки и музыка
│ ├── spawn.py # управление появлением объектов
│ ├── ui.py # интерфейс игры
│ │
│ └── managers/
│ ├── __init__.py
│ ├── base_manager.py # базовый менеджер игровых объектов
│ ├── coin_manager.py # менеджер монет
│ └── obstacle_manager.py # менеджер препятствий
│
├── .gitignore
├── .pre-commit-config.yaml # настройки pre-commit
├── .python-version # закреплённая версия Python
├── main.py # точка входа
├── pyproject.toml # настройки проекта и зависимостей
├── uv.lock # lock-файл зависимостей
└── README.md
Проект постепенно развивается от простого игрового цикла к более аккуратной архитектуре.
Точка входа в приложение.
from src.runner.game import Game
def main() -> None:
game = Game()
game.run()
if __name__ == "__main__":
main()main.py не содержит игровой логики. Он только создаёт объект игры и запускает главный цикл.
Главный координатор игры.
Класс Game отвечает за:
- инициализацию Pygame;
- создание окна;
- главный игровой цикл;
- обработку событий;
- вызов обновления объектов;
- проверку столкновений;
- вызов отрисовки;
- завершение игры.
Game не должен вручную управлять каждой монетой, каждым препятствием, каждым текстом интерфейса и каждой настройкой состояния. Для этого есть отдельные классы.
Класс GameSession хранит данные текущей игровой сессии:
- текущее состояние игры;
- текущий счёт;
- лучший результат;
- множитель скорости.
Он отвечает за действия:
self.session.start_game()
self.session.pause()
self.session.resume()
self.session.finish_game()
self.session.go_to_menu()
self.session.add_score(points)
self.session.increase_speed()Это делает код выразительнее: вместо прямого изменения строк и чисел используются методы, отражающие смысл игровых действий.
Класс UIManager отвечает за отрисовку интерфейса:
- стартовое меню;
- текущий счёт;
- лучший результат;
- множитель скорости;
- экран паузы;
- экран Game Over.
Благодаря этому Game не хранит шрифты и не содержит координаты каждого текста.
Базовый менеджер игровых объектов.
Он содержит общее поведение для групп объектов:
- создать несколько объектов;
- сбросить их позиции;
- обновить все объекты;
- пересоздать объект, если он вышел за экран;
- нарисовать все объекты.
От него наследуются:
CoinManager
ObstacleManager
Менеджер монет.
Он управляет списком монет, обновляет их, рисует и проверяет сбор игроком.
При сборе монеты счёт увеличивается, а монета появляется снова дальше по уровню.
Менеджер препятствий.
Он управляет списком препятствий, обновляет их, рисует и проверяет столкновение игрока с препятствием.
При столкновении игра переходит в состояние Game Over.
Класс, который отвечает за появление объектов справа от экрана.
Он выдаёт следующую координату по оси x с учётом случайного промежутка.
Для препятствий и монет используются разные диапазоны:
OBSTACLE_SPAWN_MIN_GAP = 450
OBSTACLE_SPAWN_MAX_GAP = 750
COIN_SPAWN_MIN_GAP = 250
COIN_SPAWN_MAX_GAP = 500Это позволяет делать препятствия более редкими, а монеты — более частыми.
Основа Pygame-игры — игровой цикл:
while self.running:
self.handle_events()
if self.session.is_playing():
self.update()
self.draw()
pygame.display.update()
self.clock.tick(FPS)Каждый кадр игра:
- обрабатывает события;
- обновляет игровой мир;
- рисует новый кадр;
- обновляет экран;
- ограничивает FPS.
Если игра находится в меню, на паузе или на экране Game Over, игровой мир не обновляется.
В проекте используются состояния:
STATE_MENU = "menu"
STATE_PLAYING = "playing"
STATE_PAUSE = "pause"
STATE_GAME_OVER = "game_over"Схема переходов:
menu
↓ Enter / Space
playing
↓ P
pause
↓ P
playing
playing
↓ столкновение с препятствием
game_over
game_over
↓ R
playing
game_over
↓ Esc
menu
Состояния помогают разделить поведение игры. В меню мы показываем стартовый экран, во время игры обновляем мир, на паузе замораживаем обновление, а после проигрыша показываем результат.
Pygame позволяет проверять столкновения через Rect:
player_hitbox.colliderect(obstacle_hitbox)Но изображения часто содержат прозрачные области. Поэтому для более честных столкновений используются хитбоксы:
PLAYER_HITBOX_INFLATE = (-40, -30)
OBSTACLE_HITBOX_INFLATE = (-30, -40)
COIN_HITBOX_INFLATE = (-15, -15)Хитбокс меньше исходного прямоугольника изображения, поэтому столкновения ощущаются точнее.
uv run python main.pyuv run python --versionuv sync --devuv pip listuv run ruff check .uv run ruff check --fix .uv run ruff format --check .uv run ruff format .uv run pre-commit installuv run pre-commit run --all-filesОсновные настройки проекта находятся в pyproject.toml.
Пример:
[project]
name = "pygame_test"
version = "0.1.0"
description = "Educational Pygame runner project"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"pygame>=2.6.1",
]
[dependency-groups]
dev = [
"pre-commit>=4.6.0",
"ruff>=0.15.17",
]
[tool.ruff]
line-length = 88
[tool.ruff.lint]
select = ["E", "W", "F", "N", "I"]
ignore = ["E501"]
[tool.ruff.format]
quote-style = "single"Перед тем как считать проект готовым, нужно пройтись по чек-листу:
1. Игра запускается через uv run python main.py.
2. При запуске появляется меню.
3. Enter или Space запускает игру.
4. Игрок анимируется.
5. Фон движется.
6. Игрок двигается влево и вправо.
7. Игрок прыгает.
8. Монеты появляются.
9. Монеты собираются.
10. Score увеличивается.
11. Препятствия появляются.
12. Столкновение с препятствием переводит игру в Game Over.
13. На экране Game Over отображается Final score.
14. Best score обновляется.
15. R запускает новую попытку.
16. Esc возвращает в меню.
17. P включает паузу.
18. Повторное P снимает паузу.
19. Музыка играет.
20. Звук прыжка работает.
21. Звук монеты работает.
22. Звук столкновения работает.
Скорее всего, проект запускается не через uv.
Правильно:
uv run python main.pyНеправильно:
python main.pyПроверь:
- файл лежит в
assets/images/; - имя файла совпадает с тем, что указано в коде;
- расширение файла совпадает:
.png,.jpg,.jpeg; - проект запускается из корня репозитория.
Проверь:
- файл лежит в
assets/sounds/; - имя файла совпадает с кодом;
- окно Pygame активно;
- звук не выключен в системе;
- формат файла поддерживается.
Проблема обычно в том, что исходный Rect изображения больше видимой части объекта.
Решение — использовать уменьшенный хитбокс:
player_hitbox = player.rect.inflate(*PLAYER_HITBOX_INFLATE)Проверь значения в constants.py:
OBSTACLE_SPAWN_MIN_GAP = 450
OBSTACLE_SPAWN_MAX_GAP = 750
COIN_SPAWN_MIN_GAP = 250
COIN_SPAWN_MAX_GAP = 500Если объекты появляются слишком редко, диапазоны можно уменьшить.
Если появляются слишком часто, диапазоны можно увеличить.
- Документация Pygame
- Документация uv
- Документация Ruff
- Документация pre-commit
- Документация GitHub Actions
После завершения курса проект можно развивать дальше:
сохранять best_score в файл;
добавить анимацию монеты;
добавить разные типы препятствий;
добавить разные уровни сложности;
добавить здоровье игрока;
добавить бонусы;
добавить выбор персонажа;
добавить экран настроек;
добавить кнопку выключения музыки;
добавить параллакс-фон;
добавить стартовый обратный отсчёт;
добавить таблицу рекордов;
собрать игру в исполняемый файл.
Курс подготовлен для студентов, изучающих Python-разработку и основы создания 2D-игр.
Преподаватель: Александр / agkondakov91