Конечные автоматы для игровой логики

Конечный автомат (Finite State Machine, FSM) представляет собой математическую модель, описывающую поведение объекта через ограниченное множество состояний и переходы между ними. В игровой логике FSM используется для управления персонажами, объектами окружения, ИИ противников и взаимодействием игровых систем.

Ключевыми компонентами FSM являются:

  • Состояния (States) – конкретные режимы поведения объекта (например, «ожидание», «движение», «атака»).
  • События (Events / Triggers) – внешние или внутренние факторы, вызывающие переход между состояниями (например, нажатие клавиши, столкновение с объектом).
  • Переходы (Transitions) – правила, определяющие, какое состояние активируется при определенном событии.
  • Действия (Actions) – код, выполняемый при входе в состояние, выходе из состояния или во время нахождения в состоянии.

FSM обеспечивает структурированность кода, упрощает сопровождение и позволяет отделить логику поведения от остальных частей игры.


Создание FSM в Babylon.js

Babylon.js не имеет встроенного класса конечного автомата, поэтому чаще всего FSM реализуется вручную через классы JavaScript или через сторонние библиотеки. Основная структура FSM выглядит следующим образом:

class State {
    constructor(name, onEn ter = null, onUpd ate = null, onE xit = null) {
        this.name = name;
        this.onEn ter = onEnter;
        this.onUpd ate = onUpdate;
        this.onE xit = onExit;
    }
}

class StateMachine {
    constructor(initialState) {
        this.currentState = initialState;
        if (this.currentState.onEnter) {
            this.currentState.onEnter();
        }
    }

    changeState(newState) {
        if (this.currentState.onExit) {
            this.currentState.onExit();
        }
        this.currentState = newState;
        if (this.currentState.onEnter) {
            this.currentState.onEnter();
        }
    }

    update() {
        if (this.currentState.onUpdate) {
            this.currentState.onUpdate();
        }
    }
}

Ключевые моменты реализации:

  • onEnter вызывается один раз при входе в состояние.
  • onUpdate выполняется каждый кадр, пока объект находится в состоянии.
  • onExit выполняется при выходе из состояния.
  • Метод update обычно вызывается внутри основного игрового цикла Babylon.js (scene.onBeforeRenderObservable.add).

Интеграция FSM с игровым персонажем

Пример реализации FSM для персонажа с состояниями «стояние», «ходьба» и «атака»:

const idleState = new State(
    "idle",
    () => console.log("Персонаж стоит"),
    () => {
        if (input.isKeyPressed("ArrowUp")) {
            fsm.changeState(walkState);
        }
    }
);

const walkState = new State(
    "walk",
    () => console.log("Персонаж идет"),
    () => {
        if (!input.isKeyPressed("ArrowUp")) {
            fsm.changeState(idleState);
        }
        if (input.isKeyPressed("Space")) {
            fsm.changeState(attackState);
        }
    }
);

const attackState = new State(
    "attack",
    () => console.log("Персонаж атакует"),
    () => {},
    () => console.log("Персонаж завершил атаку")
);

const fsm = new StateMachine(idleState);

scene.onBeforeRenderObservable.add(() => {
    fsm.update();
});

Особенности такого подхода:

  • FSM полностью контролирует поведение персонажа.
  • Добавление нового состояния требует минимальных изменений в коде.
  • Логика ввода отделена от логики поведения.

FSM для врагов и ИИ

Для NPC и врагов FSM позволяет реализовать сложное поведение, включая патрулирование, преследование игрока и реагирование на события:

const patrolState = new State(
    "patrol",
    () => startPatrol(),
    () => {
        if (playerInSight()) {
            fsm.changeState(chaseState);
        }
    }
);

const chaseState = new State(
    "chase",
    () => startChase(),
    () => {
        if (!playerInSight()) {
            fsm.changeState(patrolState);
        }
        if (playerClose()) {
            fsm.changeState(attackState);
        }
    }
);

const attackState = new State(
    "attack",
    () => performAttack(),
    () => {
        if (!playerClose()) {
            fsm.changeState(chaseState);
        }
    }
);

const fsm = new StateMachine(patrolState);
scene.onBeforeRenderObservable.add(() => fsm.update());

Преимущества FSM для ИИ:

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

Расширенные возможности

Состояния с таймерами: можно добавлять автоматический переход через заданное время:

const stunnedState = new State(
    "stunned",
    () => { this.timer = 3; },
    () => {
        this.timer -= scene.getEngine().getDeltaTime() / 1000;
        if (this.timer <= 0) fsm.changeState(idleState);
    }
);

Иерархические FSM (HFSM): состояния могут содержать вложенные FSM для более сложного поведения (например, «атака» → «подготовка», «удар», «откат»).

Комбинирование с анимациями Babylon.js: FSM идеально подходит для переключения между анимациями через scene.beginAnimation(mesh, from, to, loop) при входе в состояние.


Практические советы

  • Каждое состояние должно быть независимым и иметь четко определенные границы ответственности.
  • Переходы лучше инициировать через события или условия, а не через случайные проверки.
  • Для большого количества состояний рекомендуется использовать таблицу переходов, где строки – текущие состояния, а столбцы – события.
  • FSM легко сочетается с другими паттернами, например Команда (Command) для выполнения действий и Событийная шина (Event Bus) для внешних триггеров.

Конечные автоматы обеспечивают организованную и предсказуемую игровую логику, упрощают управление сложным поведением объектов и позволяют плавно интегрировать анимации, управление и ИИ в Babylon.js.