---
title: "Конечный автомат персонажа в платформере: от флагов isJumping к состояниям, иерархии и стеку"
description: "Как описать поведение героя 2D-платформера конечным автоматом: почему булевы флаги дают баги, таблица переходов для состояний стоит, бежит, прыжок, падение, присед, реализация через switch и через объекты-состояния с enter и exit, параллельные и иерархические автоматы, стек состояний для получения урона."
slug: konechnyj-avtomat-personazha-platformera
date: 2026-11-02
updated: 2026-11-02
scheduled_at: "2026-11-02T16:00:00+03:00"
rubric: logika-i-arhitektura
tags: ["konechnyj-avtomat", "arhitektura", "platformer", "javascript", "profi"]
reading_time: 9
draft: false
summary: "Если поведение героя держится на флагах isJumping, isDucking и isDiving, у вас 8 комбинаций при 4 допустимых, и баги вроде прыжка в воздухе неизбежны. Конечный автомат хранит одно текущее состояние и явный список переходов. Для платформера хватает пяти состояний; enter и exit убирают дублирование, параллельный автомат отделяет оружие от движения (n+m состояний вместо n×m), иерархия даёт общее суперсостояние «на земле», а стек позволяет вернуться в прежнее состояние после урона."
---

## Коротко

Конечный автомат — это персонаж, который в каждый момент находится ровно в одном состоянии из фиксированного списка и меняет его только по описанным переходам. Для героя платформера хватает пяти состояний: стоит, бежит, прыжок, падение, присед. Начинать стоит с `switch`, переходить к объектам-состояниям с действиями входа и выхода, когда состояний становится больше, а оружие и получение урона выносить в параллельный автомат и стек состояний.

## Содержание

1. Почему флаги ломаются
2. Что такое конечный автомат
3. Таблица переходов героя платформера
4. Реализация через switch
5. Объекты-состояния с enter и exit
6. Параллельные автоматы: движение и оружие
7. Иерархия: общее суперсостояние «на земле»
8. Стек состояний: урон и возврат
9. Чек-лист: как перевести героя на автомат
10. Когда вам это не подойдёт

## Почему флаги ломаются

Флаги ломаются, потому что они допускают комбинации, которых в игре быть не должно. Роберт Нистром в главе *State* книги *Game Programming Patterns* показывает это на герое с флагами прыжка, приседания и пикирования: без проверки флага герой прыгает повторно прямо в воздухе, а без ещё одной проверки приседает посреди прыжка и меняет графику (проверено: ноябрь 2026).

```text
3 булевых флага: isJumping, isDucking, isDiving
комбинаций: 2 × 2 × 2 = 8
допустимых: стоит, прыгает, приседает, пикирует = 4
недопустимых: 4 — и каждую нужно запрещать отдельной проверкой
```

С каждым новым флагом комбинаций становится вдвое больше, а проверок — всё сложнее. Автомат решает это иначе: вместо набора флагов одно поле `state`.

## Что такое конечный автомат

Конечный автомат — это фиксированный набор состояний, одно текущее состояние и переходы между ними, которые срабатывают по входу. Нистром формулирует четыре свойства: набор состояний конечен; автомат в каждый момент находится ровно в одном; на вход подаются события (нажатия клавиш, касание земли); у каждого состояния есть переходы, связывающие вход с новым состоянием. Если для пары «состояние + вход» перехода нет, вход игнорируется — именно так и исчезает прыжок в воздухе.

## Таблица переходов героя платформера

Таблица переходов — это главный артефакт автомата: её можно проверить глазами до того, как написан код.

Таблица: Переходы героя платформера

| Состояние | Вход | Новое состояние |
|---|---|---|
| Стоит | Нажата «влево/вправо» | Бежит |
| Стоит, Бежит | Нажат прыжок | Прыжок |
| Стоит, Бежит | Нажата «вниз» | Присед |
| Стоит, Бежит | Под ногами нет земли | Падение |
| Бежит | Отпущены направления | Стоит |
| Прыжок | Вертикальная скорость стала направлена вниз | Падение |
| Падение | Коснулся земли | Стоит или Бежит |
| Присед | Отпущена «вниз» | Стоит |
| Прыжок, Падение | Нажат прыжок | — (игнорируется) |

Последняя строка — та самая, которая во флаговой версии требовала отдельной проверки. В автомате её не нужно писать: у состояний «Прыжок» и «Падение» перехода по прыжку просто нет.

## Реализация через switch

Самая короткая реализация — перечисление состояний и `switch` по текущему. Нистром начинает именно с неё: вся логика одного состояния собрана в одной ветке.

```js
const State = { IDLE: 0, RUN: 1, JUMP: 2, FALL: 3, CROUCH: 4 };

function updateHero(hero, input) {
  switch (hero.state) {
    case State.IDLE:
    case State.RUN:
      if (input.jumpPressed) { hero.vy = -hero.jumpSpeed; hero.state = State.JUMP; }
      else if (input.down) { hero.state = State.CROUCH; }
      else if (!hero.onGround) { hero.state = State.FALL; }
      else { hero.state = input.dx !== 0 ? State.RUN : State.IDLE; }
      break;
    case State.JUMP:
      if (hero.vy >= 0) hero.state = State.FALL;   // прыжок здесь не обрабатывается
      break;
    case State.FALL:
      if (hero.onGround) hero.state = input.dx !== 0 ? State.RUN : State.IDLE;
      break;
    case State.CROUCH:
      if (!input.down) hero.state = State.IDLE;
      break;
  }
}
```

Минус `switch` — данные состояния. Если присед должен копить заряд для суперпрыжка, счётчик заряда придётся держать в общем объекте героя, хотя нужен он только одному состоянию.

## Объекты-состояния с enter и exit

Объект-состояние — это отдельный объект на каждое состояние со своими данными и методами; герой лишь хранит ссылку на текущий. Это паттерн «Состояние» из книги «банды четырёх», и Нистром разбирает его как следующий шаг после `switch`. Два метода делают его особенно удобным: `enter` вызывается при входе в состояние, `exit` — при выходе. В `enter` удобно один раз поменять анимацию и хитбокс, вместо того чтобы делать это в каждом переходе, ведущем в состояние.

```js
class CrouchState {
  enter(hero) { this.charge = 0; hero.setAnimation("crouch"); hero.setHitbox("low"); }
  exit(hero)  { hero.setHitbox("normal"); }
  update(hero, input, dt) {
    this.charge += dt;                         // данные живут в самом состоянии
    if (!input.down) return this.charge > 0.5 ? new SuperJumpState() : new IdleState();
    return null;                               // остаёмся
  }
}

function changeState(hero, next) {
  hero.state.exit(hero);
  hero.state = next;
  hero.state.enter(hero);
}

function tick(hero, input, dt) {
  const next = hero.state.update(hero, input, dt);
  if (next) changeState(hero, next);
}
```

Нистром отдельно обсуждает выбор между статическими состояниями (один общий объект, если у состояния нет данных) и создаваемыми на каждый переход (если данные есть, как заряд приседа). Для героя-одиночки создание объекта на переход ничего не стоит; для сотен врагов статические состояния экономят память и сборку мусора.

## Параллельные автоматы: движение и оружие

Если герой может бегать, прыгать и при этом держать оружие, нужны два автомата, а не один большой. Нистром показывает: если склеить в одном автомате `n` состояний движения и `m` состояний оружия, получится `n × m` состояний; два параллельных автомата дают `n + m`.

```text
движение: стоит, бежит, прыжок, падение, присед   n = 5
оружие:   без оружия, пистолет, перезарядка       m = 3

один автомат:  5 × 3 = 15 состояний
два автомата:  5 + 3 = 8 состояний
```

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

## Иерархия: общее суперсостояние «на земле»

Иерархический автомат убирает дублирование одинаковых переходов. В таблице выше «Стоит» и «Бежит» одинаково реагируют на прыжок, «вниз» и потерю опоры. Нистром предлагает суперсостояние `OnGround`: оно обрабатывает общие входы, а дочерние состояния — только свои. Если дочернее состояние вход не обработало, он передаётся родителю.

```js
class OnGroundState {
  update(hero, input) {
    if (input.jumpPressed) { hero.vy = -hero.jumpSpeed; return new JumpState(); }
    if (input.down) return new CrouchState();
    if (!hero.onGround) return new FallState();
    return null;
  }
}

class RunState extends OnGroundState {
  update(hero, input, dt) {
    if (input.dx === 0) return new IdleState();
    return super.update(hero, input, dt);      // общие переходы — у родителя
  }
}
```

## Стек состояний: урон и возврат

Стек состояний нужен, когда после временного состояния персонаж должен вернуться туда, где был. Нистром разбирает это как автомат с магазинной памятью на примере стрельбы: новое состояние кладётся поверх стека, а по окончании снимается, и герой продолжает прежнее. Для платформера тот же приём подходит к получению урона: отброс и мигание кладутся поверх «бежит» или «падение», а после снятия автомат продолжает с того состояния, которое лежало ниже.

```js
const stack = [new IdleState()];
const current = () => stack[stack.length - 1];

function push(hero, s) { current().exit(hero); stack.push(s); s.enter(hero); }
function pop(hero)     { current().exit(hero); stack.pop(); current().enter(hero); }

// при попадании: push(hero, new HurtState(0.4));
// HurtState через 0,4 с вызывает pop(hero)
```

Подвох — устаревшее состояние: если за время урона герой упал с платформы, нижнее «бежит» уже неверно. Поэтому в `enter` нижнего состояния полезно заново проверить условия (есть ли земля) и сразу перейти дальше.

## Чек-лист: как перевести героя на автомат

Задача: заменить флаги героя автоматом и убедиться, что исчезли недопустимые комбинации. Проверяемый результат — тест, который 1000 раз жмёт случайные клавиши, и ни разу не встречает «прыжок в воздухе» и «присед в прыжке».

1. **Выпишите все флаги** героя и посчитайте комбинации: `2` в степени числа флагов.
2. **Отметьте допустимые** — это и есть список состояний.
3. **Составьте таблицу переходов** до кода; пустая клетка означает «вход игнорируется».
4. **Начните со `switch`**, если состояний до шести.
5. **Перенесите смену анимации и хитбокса в `enter`**, как только одно и то же присваивание встречается в трёх и более переходах.
6. **Выделите параллельный автомат**, если появилось оружие или другой независимый аспект.
7. **Добавьте стек** для урона и других временных состояний; в `enter` нижнего состояния перепроверяйте условия.
8. **Добавьте в отладочный вывод** имя текущего состояния: половина багов поведения видна сразу.

Coyote time и буфер прыжка из статьи о прыжке хорошо ложатся на эту схему: окно coyote time — это время, которое состояние «Падение» после схода с края ещё принимает вход прыжка.

## Когда вам это не подойдёт

Конечный автомат хорош для героя и простых врагов, но не для всего. Случаи, когда лучше выбрать другое:

- **Сложный ИИ с целями и приоритетами.** Нистром прямо пишет, что автоматы не Тьюринг-полны, и для сложного ИИ советует деревья поведения и системы планирования.
- **Очень простой объект.** Пуле или монетке с одним поведением автомат не нужен: хватает одного `update`.
- **Поведение задаётся данными дизайнера.** Если дизайнеры правят логику без программиста, удобнее таблица переходов в файле данных или визуальный редактор логики, а код читает её.
- **Физика вместо логики.** Если прыжок и падение полностью определяются физическим движком, состояние можно вычислять из скорости и контакта с землёй, а не хранить.

Мы разрабатываем **J2DRU** — браузерный 2D игровой движок с событийной логикой и путём к TypeScript; он в разработке и публично пока не открыт, заявку можно оставить на [странице раннего доступа](/early-access?utm_source=blog&utm_medium=article&utm_campaign=konechnyj-avtomat-personazha-platformera) (подача заявки не гарантирует приглашение).

**Источники (проверено: ноябрь 2026):** Robert Nystrom, *Game Programming Patterns*, глава *State* (gameprogrammingpatterns.com/state.html) — флаги и их баги, определение автомата, enum и switch, паттерн «Состояние», enter и exit, параллельные, иерархические и магазинные автоматы, ограничения автоматов. Таблица переходов, подсчёт комбинаций и код — наш пример.

**Читайте также:**
- [Идеальный прыжок и секрет Coyote Time](/blog/idealnyj-pryzhok-i-sekret-coyote-time)
- [Патрулирование врагов и читаемые опасности](/blog/patrulirovanie-vragov-i-chitaemye-opasnosti)
- [Игровой цикл и физика столкновений AABB](/blog/igrovoj-cikl-i-fizika-stolknovenij-aabb)
