---
id: state-machines
lang: ru
title: Состояния и переходы
description: >-
  Описываем допустимые состояния персонажа, приоритет событий и проверяем
  переходы без хаоса из флагов.
category: foundations
level: beginner
minutes: 6
updatedAt: '2026-10-20'
tags:
  - state-machines
  - architecture
  - testing
sources:
  - title: 'Robert Nystrom: State (author repository)'
    url: >-
      https://github.com/munificent/game-programming-patterns/blob/master/book/state.markdown
url: 'https://gamemechanics.pro/ru/lessons/state-machines/'
markdownUrl: 'https://gamemechanics.pro/content/ru/state-machines.md'
license: CC-BY-4.0
codeLicense: MIT
attribution: gamemechanics.pro contributors
---

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

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

## Принцип

Набор флагов `isDashing`, `isDead`, `canMove` легко допускает противоречия. Конечный автомат хранит одно состояние выбранной системы и меняет его по явно заданным событиям. Переход может иметь условие допуска, а вход и выход могут выполнять разовые действия. Основа подхода разобрана автором Game Programming Patterns в [главе State](https://github.com/munificent/game-programming-patterns/blob/master/book/state.markdown).

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

<span id="пример-короткий-рывок"></span>

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

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

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

| Текущее состояние | Событие и условие            | Следующее состояние       |
| ----------------- | ---------------------------- | ------------------------- |
| ready             | dashPressed и заряд доступен | dashing                   |
| dashing           | время рывка истекло          | ready                     |
| ready или dashing | здоровье стало нулевым       | dead                      |
| dead              | restart                      | ready после сброса данных |

```text
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 и повторный старт. Объясни, какие данные автомат хранит отдельно от состояния.
