Очки, здоровье и состояние игры

Состояние игры — это её память: сколько очков, сколько здоровья, какой уровень. Его удобно хранить в одном месте.

Суть: общие данные (счёт, жизни) выносят в один глобальный узел-автозагрузку, чтобы к ним был доступ из любой сцены.

Очки и здоровье нужны многим частям игры: их показывает интерфейс, их меняют монетки и враги, по ним игра решает, не пора ли показать экран проигрыша. Если хранить эти данные внутри сцены уровня, то при переходе на следующий уровень они потеряются. Решение — автозагрузка (autoload), часто называемая синглтоном: специальный узел, который существует в единственном экземпляре и доступен из любой сцены по имени, например GameState.

extends Node  # автозагрузка GameState

signal score_changed(value: int)
signal game_over

var score: int = 0
var health: int = 100

func add_score(amount: int) -> void:
    score += amount
    score_changed.emit(score)

func take_damage(amount: int) -> void:
    health = max(health - amount, 0)
    if health == 0:
        game_over.emit()

Теперь любая монетка зовёт GameState.add_score(10), любой враг — GameState.take_damage(20), а интерфейс слушает score_changed и обновляет надпись. Состояние в одном месте, все общаются через него и через сигналы.

Полезно увидеть в автозагрузке единый источник правды. Когда счёт хранится в одном месте, не бывает ситуации «интерфейс показывает 100 очков, а логика думает, что их 80». Все смотрят на одну и ту же переменную и узнают о её изменении через один и тот же сигнал. Этот принцип — «одно состояние, много наблюдателей» — лежит в основе не только игр, но и больших приложений вообще. Освоив его на маленькой игре, ты получаешь привычку, которая пригодится в любом серьёзном проекте: данные живут в одном месте, а все остальные части лишь подписываются на их изменения.

Как работает под капотом

Автозагрузка — это узел, который движок создаёт один раз при старте игры и держит на самом верху дерева, выше всех сцен. При смене уровня сцена уровня выгружается, а автозагрузка остаётся жить, сохраняя данные. Доступ по имени GameState работает потому, что движок регистрирует автозагрузку как глобально видимое имя.

class GameState:
    def __init__(self):
        self.score = 0
        self.health = 100

    def add_score(self, amount):
        self.score += amount
        print(f"Очки: {self.score}")

    def take_damage(self, amount):
        self.health = max(self.health - amount, 0)
        print(f"Здоровье: {self.health}")
        if self.health == 0:
            print("ИГРА ОКОНЧЕНА")

game = GameState()      # один на всю игру
game.add_score(10)
game.add_score(25)
game.take_damage(60)
game.take_damage(50)    # уводит здоровье в 0

Та же логика на Python ▶. Один объект GameState хранит счёт и здоровье; все части игры зовут его методы. Заметь max(..., 0) — он не даёт здоровью уйти в минус. Запусти и проследи путь до «Игра окончена».

Стоит добавить осторожности: автозагрузка — мощный, но коварный инструмент. Поскольку к ней есть доступ отовсюду, велик соблазн складывать туда всё подряд, и со временем она превращается в свалку, от которой зависит вся игра. Поэтому держи там только по-настоящему общее: счёт, здоровье, текущий уровень, глобальные настройки. Всё, что относится к конкретной сцене, пусть живёт в этой сцене. Хорошее правило: если данные нужны лишь одному уровню — им не место в автозагрузке. Так глобальное состояние остаётся маленьким, понятным и не превращается в источник запутанных ошибок.

Частые ошибки

Первая ошибка — копировать состояние в каждую сцену: при переходе данные рассинхронизируются, счёт «прыгает». Держи одно состояние в автозагрузке. Вторая — давать здоровью уходить в минус: всегда ограничивай через max(health, 0). Третья — забыть эмитить сигнал об изменении, и тогда интерфейс не обновляется: меняешь данные — шли сигнал. Четвёртая — пихать в автозагрузку вообще всё подряд: туда кладут только по-настоящему глобальное состояние, а не каждую мелочь уровня.

Best practices

Выноси в автозагрузку только общие данные: счёт, здоровье, текущий уровень, настройки. Меняй состояние через методы (add_score, take_damage), а не правь переменные снаружи напрямую — так логика в одном месте. На каждое значимое изменение эмить сигнал, чтобы интерфейс и звук реагировали. Ограничивай значения (здоровье не ниже нуля, очки не отрицательные) прямо в методах.

Итоги: состояние игры — это её память (счёт, здоровье, уровень), и его держат в одной автозагрузке-синглтоне, доступной из любой сцены. Меняй данные через методы и эмить сигналы об изменениях. Автозагрузка переживает смену сцен. Ограничивай значения (max(health, 0)) и не дублируй состояние по сценам.

Проверьте себя
1. Зачем хранить счёт и здоровье в автозагрузке (синглтоне), а не внутри сцены уровня?
AТак требует движок
BЧтобы данные пережили смену уровня и были доступны из любой сцены
CАвтозагрузка делает игру быстрее
DЧтобы спрятать данные от игрока
2. Зачем в take_damage писать health = max(health - amount, 0)?
AЧтобы здоровье росло
BЧтобы здоровье не уходило в отрицательные значения
CЧтобы ускорить вычисления
DЭто случайная привычка