Очки, жизни и конец игры
Игра без целей скучна. Подсчёт очков, жизни и условие проигрыша превращают набор спрайтов в настоящую игру с азартом.
Суть: игровое состояние — это переменные score, lives и флаг game_over. События мира (собрал монетку, получил урон) меняют их, а цикл проверяет условия победы и поражения.
Спрайты двигаются, сталкиваются, звучат — но пока это не игра, а песочница. Игрой её делает цель и риск: набирай очки, береги жизни, не проиграй. Всё это живёт в нескольких простых переменных, которые мы называем игровым состоянием. Главные из них — score (счёт), lives (жизни) и game_over (закончилась ли игра).
Логика проста и понятна. Собрал монетку — score += 10. Столкнулся с врагом — lives -= 1. Когда lives доходит до нуля — game_over = True, и мы показываем экран поражения. Если игрок выполнил цель (набрал нужный счёт, добрался до выхода) — победа. Всё это обычные проверки в игровом цикле, никакой магии.
Часто заводят и систему уровней сложности: каждые N очков враги становятся быстрее или появляются чаще. Это держит игрока в напряжении и не даёт заскучать. Вся эта «душа» игры — чистая логика на числах, и её приятнее всего отлаживать без графики.
Как работает под капотом
Состояние — это набор переменных. События мира меняют их, а цикл каждый кадр проверяет условия конца игры:
СОБЫТИЯ МИРА СОСТОЯНИЕ ПРОВЕРКИ
собрал монету --> score += 10
убил врага --> score += 50 --> score >= GOAL? ПОБЕДА
получил урон --> lives -= 1 --> lives <= 0? ПОРАЖЕНИЕ
|
v
game_over = True
В pygame это вплетается в цикл (читаем):
score = 0
lives = 3
game_over = False
# в обновлении:
coins_hit = pygame.sprite.spritecollide(player, coins, True)
score += len(coins_hit) * 10
if pygame.sprite.spritecollide(player, enemies, False):
lives -= 1
if lives <= 0:
game_over = TrueВсю игровую логику очков и жизней можно прогнать без графики — это идеальный кандидат для проверки. Сымитируем серию событий и посчитаем исход. Попробуй сам:
GOAL = 100
state = {"score": 0, "lives": 3, "over": False, "win": False}
def apply(event, state):
if state["over"]: return state
if event == "coin": state["score"] += 10
if event == "enemy_kill": state["score"] += 50
if event == "damage":
state["lives"] -= 1
if state["lives"] <= 0: state["over"] = True
if state["score"] >= GOAL:
state["win"] = True; state["over"] = True
return state
events = ["coin", "coin", "damage", "enemy_kill", "coin", "coin", "coin"]
for e in events:
state = apply(e, state)
print(f"{e:11} -> счёт={state['score']:3} жизни={state['lives']} победа={state['win']}")Кривая сложности и удержание игрока
Хорошая игра не стоит на месте по сложности — она плавно растёт, держа игрока в «потоке»: не слишком легко, чтобы не скучать, и не слишком трудно, чтобы не злиться. Реализуют это через числа состояния: каждые N очков увеличивай скорость врагов или частоту их появления. Простая формула вроде «интервал спавна = базовый минус счёт, делённый на коэффициент» уже даёт ощутимое нарастание напряжения без всякой сложной системы.
Не забудь и про обратную связь на каждое событие. Собрал монетку — звяк и плюс к счёту; потерял жизнь — вспышка и звук урона; новый рекорд — отдельная надпись. Игрок должен мгновенно понимать последствия своих действий, иначе игра ощущается «глухой». Сохранение рекорда в файл между запусками (через обычную запись числа в текстовый файл) добавляет азарта «побей себя вчерашнего». Вся эта душа игры — это работа с несколькими числами, и именно поэтому её так приятно отлаживать без графики, как ты делал в запускаемом примере.
Частые ошибки
- Уменьшать жизни каждый кадр контакта — за одно касание врага игрок теряет все жизни мгновенно. Нужна неуязвимость или dokill.
- Не останавливать логику после game_over — счёт продолжает капать на «мёртвом» экране.
- Хранить состояние в куче глобалок — лучше собрать в словарь или класс.
Best practices
- Собери состояние игры в одном месте (словарь или класс) — легче читать и отлаживать.
- Проверяй условия победы/поражения раз за кадр, после обновления мира.
- Добавь короткую неуязвимость после урона, чтобы жизни не сгорали за кадр.
Итог: игру делают цель и риск, а живут они в переменных score, lives, game_over. Эта логика — чистые числа, и проверять её можно без единого спрайта.