Собираем игру целиком
Соберём всё изученное в одну рабочую игру: герой, враги, монетки, счёт, состояния. Это карта того, как куски складываются в целое.
Суть: готовая игра — это игровой цикл, внутри которого по состоянию вызываются обновление и отрисовка, а спрайт-группы хранят героя, врагов и монетки. Все уроки складываются в одну структуру.
Пора увидеть лес за деревьями. За курс мы собрали много деталей: цикл, dt, спрайты, коллизии, гравитацию, звук, счёт, состояния, ИИ. Теперь посмотрим, как они складываются в одну работающую игру — простой аркадный собиратель монет с врагами. Это не новый материал, а карта, показывающая место каждого кусочка.
Структура такая. Сверху — машина состояний: меню, игра, конец. В состоянии "playing" крутится знакомый цикл: события → обновление → отрисовка. Обновление двигает героя по вводу, обновляет группу врагов (с их ИИ) и монеток, проверяет коллизии (собрал монету — плюс очки, коснулся врага — минус жизнь). Отрисовка заливает фон, рисует все группы и HUD со счётом. Звук играет на событиях.
Каждый объект — спрайт в своей группе. Герой управляется клавишами и вектором скорости, враги используют простой ИИ из прошлого урока, монетки просто ждут сбора. Всё это знакомо — мы лишь соединяем провода.
Как работает под капотом
Вот карта всей игры: состояние сверху, цикл внутри, группы и подсистемы под ним:
[МАШИНА СОСТОЯНИЙ] menu / playing / game_over
|
v (если playing)
+-------------------------------------------+
| ИГРОВОЙ ЦИКЛ |
| события -> обновление -> отрисовка -> flip|
+-------------------------------------------+
| | |
v v v
ввод игрока группы: фон + группы
(клавиши) player + HUD (счёт)
enemies (ИИ)
coins
коллизии -> счёт, жизни
Скелет главного файла (читаем):
def update_world(dt):
player.update(dt)
enemies.update(dt, player.rect.center) # ИИ врагов
got = pygame.sprite.spritecollide(player, coins, True)
state["score"] += len(got) * 10
if pygame.sprite.spritecollide(player, enemies, False):
state["lives"] -= 1
while running:
dt = clock.tick(60) / 1000.0
handle_events()
if game_state == "playing":
update_world(dt)
draw(game_state)
pygame.display.flip()Соберём «тик» всей игры одним прогоном без графики: обработаем кадр, в котором герой собрал монетку и задел врага. Попробуй сам:
def game_tick(state, events):
if state["lives"] <= 0:
state["over"] = True
return state
state["score"] += events.get("coins", 0) * 10
state["lives"] -= events.get("enemy_hits", 0)
if state["lives"] <= 0:
state["over"] = True
return state
state = {"score": 0, "lives": 3, "over": False}
frames = [
{"coins": 2, "enemy_hits": 0},
{"coins": 1, "enemy_hits": 1},
{"coins": 0, "enemy_hits": 0},
]
for i, ev in enumerate(frames, 1):
state = game_tick(state, ev)
print(f"кадр {i}: счёт={state['score']} жизни={state['lives']} конец={state['over']}")Раскладка файлов растущего проекта
Пока игра помещается в один файл — это нормально. Но как только появятся классы игрока, врагов, монеток, сцен и настроек, держать всё в одном main.py станет тяжело. Зрелая раскладка разносит код по смыслу: main.py с игровым циклом, player.py, enemy.py, settings.py с константами, папка assets/ для картинок и звуков. Каждый файл отвечает за свою часть, и найти нужное становится легко даже спустя месяц.
Особенно полезен отдельный settings.py, куда вынесены все «магические числа»: размер окна, скорости, гравитация, цвета, пути к ассетам. Когда все настройки в одном месте, балансировать игру — одно удовольствие: подкрутил пару чисел, и не нужно искать их по всему коду. Это та же идея, что и с константами WIDTH и HEIGHT из первых уроков, только в масштабе всего проекта. Аккуратная структура — не бюрократия, а забота о себе будущем: именно она решает, доведёшь ли ты игру до конца или утонешь в собственном коде на половине пути.
Частые ошибки
- Свалить всё в один гигантский файл-цикл — раздели на функции: события, обновление, отрисовка.
- Обновлять мир вне "playing" — враги двигаются в меню.
- Грузить ассеты в цикле — грузи всё при старте.
Best practices
- Раздели код на функции
handle_events,update_world,draw. - Держи объекты в группах по смыслу, коллизии — через spritecollide.
- Состояние игры держи в одном словаре или классе — легче рестартить.
Итог: готовая игра — это состояния сверху, цикл внутри, спрайт-группы и подсистемы под ним. Все уроки курса встают на свои места в этой структуре.