Очки, жизни и конец игры

Игра без целей скучна. Подсчёт очков, жизни и условие проигрыша превращают набор спрайтов в настоящую игру с азартом.
Суть: игровое состояние — это переменные 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. Эта логика — чистые числа, и проверять её можно без единого спрайта.

Проверьте себя
1. Где живёт «душа» игры — счёт, жизни, условие проигрыша?
AВ картинках спрайтов
BВ переменных игрового состояния (score, lives, game_over)
CВ звуковых файлах
DВ заголовке окна
2. Почему опасно уменьшать lives каждый кадр контакта с врагом?
AЭто замедляет игру
BЗа одно касание сгорят все жизни сразу — нужна неуязвимость или dokill
CЗвук пропадёт
DОкно закроется