Коротко
Конечный автомат — это персонаж, который в каждый момент находится ровно в одном состоянии из фиксированного списка и меняет его только по описанным переходам. Для героя платформера хватает пяти состояний: стоит, бежит, прыжок, падение, присед. Начинать стоит с switch, переходить к объектам-состояниям с действиями входа и выхода, когда состояний становится больше, а оружие и получение урона выносить в параллельный автомат и стек состояний.
Содержание
- Почему флаги ломаются
- Что такое конечный автомат
- Таблица переходов героя платформера
- Реализация через switch
- Объекты-состояния с enter и exit
- Параллельные автоматы: движение и оружие
- Иерархия: общее суперсостояние «на земле»
- Стек состояний: урон и возврат
- Чек-лист: как перевести героя на автомат
- Когда вам это не подойдёт
Почему флаги ломаются
Флаги ломаются, потому что они допускают комбинации, которых в игре быть не должно. Роберт Нистром в главе State книги Game Programming Patterns показывает это на герое с флагами прыжка, приседания и пикирования: без проверки флага герой прыгает повторно прямо в воздухе, а без ещё одной проверки приседает посреди прыжка и меняет графику (проверено: ноябрь 2026).
3 булевых флага: isJumping, isDucking, isDiving
комбинаций: 2 × 2 × 2 = 8
допустимых: стоит, прыгает, приседает, пикирует = 4
недопустимых: 4 — и каждую нужно запрещать отдельной проверкой
С каждым новым флагом комбинаций становится вдвое больше, а проверок — всё сложнее. Автомат решает это иначе: вместо набора флагов одно поле state.
Что такое конечный автомат
Конечный автомат — это фиксированный набор состояний, одно текущее состояние и переходы между ними, которые срабатывают по входу. Нистром формулирует четыре свойства: набор состояний конечен; автомат в каждый момент находится ровно в одном; на вход подаются события (нажатия клавиш, касание земли); у каждого состояния есть переходы, связывающие вход с новым состоянием. Если для пары «состояние + вход» перехода нет, вход игнорируется — именно так и исчезает прыжок в воздухе.
Таблица переходов героя платформера
Таблица переходов — это главный артефакт автомата: её можно проверить глазами до того, как написан код.
| Состояние | Вход | Новое состояние |
|---|---|---|
| Стоит | Нажата «влево/вправо» | Бежит |
| Стоит, Бежит | Нажат прыжок | Прыжок |
| Стоит, Бежит | Нажата «вниз» | Присед |
| Стоит, Бежит | Под ногами нет земли | Падение |
| Бежит | Отпущены направления | Стоит |
| Прыжок | Вертикальная скорость стала направлена вниз | Падение |
| Падение | Коснулся земли | Стоит или Бежит |
| Присед | Отпущена «вниз» | Стоит |
| Прыжок, Падение | Нажат прыжок | — (игнорируется) |
Последняя строка — та самая, которая во флаговой версии требовала отдельной проверки. В автомате её не нужно писать: у состояний «Прыжок» и «Падение» перехода по прыжку просто нет.
Реализация через switch
Самая короткая реализация — перечисление состояний и switch по текущему. Нистром начинает именно с неё: вся логика одного состояния собрана в одной ветке.
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 удобно один раз поменять анимацию и хитбокс, вместо того чтобы делать это в каждом переходе, ведущем в состояние.
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.
движение: стоит, бежит, прыжок, падение, присед n = 5
оружие: без оружия, пистолет, перезарядка m = 3
один автомат: 5 × 3 = 15 состояний
два автомата: 5 + 3 = 8 состояний
Работает это, пока автоматы почти не зависят друг от друга. Если «перезарядку нельзя начинать в прыжке», одному автомату придётся спросить состояние другого — это допустимо, но признак того, что зависимостей стало много.
Иерархия: общее суперсостояние «на земле»
Иерархический автомат убирает дублирование одинаковых переходов. В таблице выше «Стоит» и «Бежит» одинаково реагируют на прыжок, «вниз» и потерю опоры. Нистром предлагает суперсостояние OnGround: оно обрабатывает общие входы, а дочерние состояния — только свои. Если дочернее состояние вход не обработало, он передаётся родителю.
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); // общие переходы — у родителя
}
}
Стек состояний: урон и возврат
Стек состояний нужен, когда после временного состояния персонаж должен вернуться туда, где был. Нистром разбирает это как автомат с магазинной памятью на примере стрельбы: новое состояние кладётся поверх стека, а по окончании снимается, и герой продолжает прежнее. Для платформера тот же приём подходит к получению урона: отброс и мигание кладутся поверх «бежит» или «падение», а после снятия автомат продолжает с того состояния, которое лежало ниже.
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 раз жмёт случайные клавиши, и ни разу не встречает «прыжок в воздухе» и «присед в прыжке».
- Выпишите все флаги героя и посчитайте комбинации:
2в степени числа флагов. - Отметьте допустимые — это и есть список состояний.
- Составьте таблицу переходов до кода; пустая клетка означает «вход игнорируется».
- Начните со
switch, если состояний до шести. - Перенесите смену анимации и хитбокса в
enter, как только одно и то же присваивание встречается в трёх и более переходах. - Выделите параллельный автомат, если появилось оружие или другой независимый аспект.
- Добавьте стек для урона и других временных состояний; в
enterнижнего состояния перепроверяйте условия. - Добавьте в отладочный вывод имя текущего состояния: половина багов поведения видна сразу.
Coyote time и буфер прыжка из статьи о прыжке хорошо ложатся на эту схему: окно coyote time — это время, которое состояние «Падение» после схода с края ещё принимает вход прыжка.
Когда вам это не подойдёт
Конечный автомат хорош для героя и простых врагов, но не для всего. Случаи, когда лучше выбрать другое:
- Сложный ИИ с целями и приоритетами. Нистром прямо пишет, что автоматы не Тьюринг-полны, и для сложного ИИ советует деревья поведения и системы планирования.
- Очень простой объект. Пуле или монетке с одним поведением автомат не нужен: хватает одного
update. - Поведение задаётся данными дизайнера. Если дизайнеры правят логику без программиста, удобнее таблица переходов в файле данных или визуальный редактор логики, а код читает её.
- Физика вместо логики. Если прыжок и падение полностью определяются физическим движком, состояние можно вычислять из скорости и контакта с землёй, а не хранить.
Мы разрабатываем J2DRU — браузерный 2D игровой движок с событийной логикой и путём к TypeScript; он в разработке и публично пока не открыт, заявку можно оставить на странице раннего доступа (подача заявки не гарантирует приглашение).
Источники (проверено: ноябрь 2026): Robert Nystrom, Game Programming Patterns, глава State (gameprogrammingpatterns.com/state.html) — флаги и их баги, определение автомата, enum и switch, паттерн «Состояние», enter и exit, параллельные, иерархические и магазинные автоматы, ограничения автоматов. Таблица переходов, подсчёт комбинаций и код — наш пример.
Читайте также: