← Все материалы
Основы

Состояния и переходы

Описываем допустимые состояния персонажа, приоритет событий и проверяем переходы без хаоса из флагов.

Что нужно знать

Нужны условия, функции и простые перечисления. Для упражнения достаточно персонажа, который ходит, выполняет рывок и погибает. Результат урока: таблица переходов, из которой видно, какое поведение разрешено в каждый момент.

Принцип

Набор флагов isDashing, isDead, canMove легко допускает противоречия. Конечный автомат хранит одно состояние выбранной системы и меняет его по явно заданным событиям. Переход может иметь условие допуска, а вход и выход могут выполнять разовые действия. Основа подхода разобрана автором Game Programming Patterns в главе State.

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

Практический пример

Короткий рывок

Предположим, рывок длится 0,18 секунды, а смерть немедленно его прерывает. Это параметры учебного примера, которые нужно подобрать для своей игры.

Текущее состояние Событие и условие Следующее состояние
ready dashPressed и заряд доступен dashing
dashing время рывка истекло ready
ready или dashing здоровье стало нулевым dead
dead restart ready после сброса данных
update(dt, events):
    if state == dead and events.restart:
        resetCharacterData()
        transitionTo(ready)
        return
    if health <= 0:
        transitionTo(dead)
        return
    if state == ready and events.dashPressed and chargeReady:
        transitionTo(dashing)
    else if state == dashing:
        dashRemaining -= dt
        if dashRemaining <= 0:
            transitionTo(ready)

Функция transitionTo ничего не делает, если новое состояние равно текущему. При настоящем переходе она вызывает выход, меняет состояние, затем выполняет вход. Заряд списывается при входе в dashing, а не на каждом кадре. Направление рывка можно зафиксировать тогда же.

Обратите внимание на порядок. Проверка смерти выполняется первой и завершает обновление. Это делает ответ на одновременные события определённым. Другой игре может требоваться другой приоритет, но его необходимо записать.

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

Анимация не должна случайно воскрешать персонажа, отправляя запоздалое событие завершения рывка. Принимайте событие только в подходящем состоянии; для отложенных callback (функций, вызываемых после события) полезно также проверять идентификатор действия.

Не создавайте общий базовый класс для десятков гипотетических состояний. Начните с перечисления и небольшой функции перехода. Если таблица превращается в комбинации «бег с оружием», «бег без оружия», проверьте, не смешаны ли независимые системы.

При рестарте недостаточно присвоить ready: нужно восстановить здоровье, заряд, таймеры и подписки. Составьте явный список сбрасываемых данных и проверьте его вторым запуском сцены.

Проверка результата

  • Один запрос рывка расходует ровно один заряд.
  • Смерть прерывает рывок и запрещает новые действия.
  • Позднее событие анимации не меняет состояние погибшего персонажа.
  • Каждая строка таблицы проверяется отдельно.
  • Недопустимые события имеют определённый результат: игнорирование или диагностическое сообщение.
  • Рестарт возвращает полный начальный набор данных.

Задание для ИИ-агента

Найди флаги, управляющие рывком и смертью. Сначала составь таблицу допустимых переходов и приоритет одновременных событий. Затем предложи небольшой автомат без общего фреймворка. Проверь двойное нажатие, смерть в середине рывка, запоздалый callback и повторный старт. Объясни, какие данные автомат хранит отдельно от состояния.

Источники и дальнейшее чтение

CC BY 4.0 · Game Dev DB contributors