{"schemaVersion":1,"record":{"key":"lesson/state-machines","type":"lesson","id":"state-machines","lang":"ru","title":"Состояния и переходы","summary":"Описываем допустимые состояния персонажа, приоритет событий и проверяем переходы без хаоса из флагов.","updatedAt":"2026-10-20","status":"active","url":"https://gamemechanics.pro/ru/lessons/state-machines/","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/ru/lesson/state-machines.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/ru/lesson/state-machines.md","aliases":[],"tags":["state-machines","architecture","testing"],"sourceIds":["source-3817d420df0baa92"],"sources":[{"id":"source-3817d420df0baa92","title":"Robert Nystrom: State (author repository)","url":"https://github.com/munificent/game-programming-patterns/blob/master/book/state.markdown","verifiedAt":null}],"sections":[{"id":"prerequisites","title":"Что нужно знать","body":"Нужны условия, функции и простые перечисления. Для упражнения достаточно персонажа, который ходит, выполняет рывок и погибает. Результат урока: таблица переходов, из которой видно, какое поведение разрешено в каждый момент."},{"id":"principle","title":"Принцип","body":"Набор флагов `isDashing`, `isDead`, `canMove` легко допускает противоречия. Конечный автомат хранит одно состояние выбранной системы и меняет его по явно заданным событиям. Переход может иметь условие допуска, а вход и выход могут выполнять разовые действия. Основа подхода разобрана автором Game Programming Patterns в [главе State](https://github.com/munificent/game-programming-patterns/blob/master/book/state.markdown).\n\nНе пытайтесь одним перечислением описать вообще всё. Состояние перемещения и состояние открытого меню могут быть независимы. Сначала определите границу: например, автомат управляет только доступностью действий персонажа. Здоровье, позиция и остаток времени рывка остаются данными.\n\n<span id=\"пример-короткий-рывок\"></span>"},{"id":"example","title":"Практический пример","body":"### Короткий рывок\n\nПредположим, рывок длится 0,18 секунды, а смерть немедленно его прерывает. Это параметры учебного примера, которые нужно подобрать для своей игры.\n\n| Текущее состояние | Событие и условие            | Следующее состояние       |\n| ----------------- | ---------------------------- | ------------------------- |\n| ready             | dashPressed и заряд доступен | dashing                   |\n| dashing           | время рывка истекло          | ready                     |\n| ready или dashing | здоровье стало нулевым       | dead                      |\n| dead              | restart                      | ready после сброса данных |\n\n```text\nupdate(dt, events):\n    if state == dead and events.restart:\n        resetCharacterData()\n        transitionTo(ready)\n        return\n    if health <= 0:\n        transitionTo(dead)\n        return\n    if state == ready and events.dashPressed and chargeReady:\n        transitionTo(dashing)\n    else if state == dashing:\n        dashRemaining -= dt\n        if dashRemaining <= 0:\n            transitionTo(ready)\n```\n\nФункция `transitionTo` ничего не делает, если новое состояние равно текущему. При настоящем переходе она вызывает выход, меняет состояние, затем выполняет вход. Заряд списывается при входе в `dashing`, а не на каждом кадре. Направление рывка можно зафиксировать тогда же.\n\nОбратите внимание на порядок. Проверка смерти выполняется первой и завершает обновление. Это делает ответ на одновременные события определённым. Другой игре может требоваться другой приоритет, но его необходимо записать."},{"id":"mistakes","title":"Частые ошибки","body":"Анимация не должна случайно воскрешать персонажа, отправляя запоздалое событие завершения рывка. Принимайте событие только в подходящем состоянии; для отложенных callback (функций, вызываемых после события) полезно также проверять идентификатор действия.\n\nНе создавайте общий базовый класс для десятков гипотетических состояний. Начните с перечисления и небольшой функции перехода. Если таблица превращается в комбинации «бег с оружием», «бег без оружия», проверьте, не смешаны ли независимые системы.\n\nПри рестарте недостаточно присвоить `ready`: нужно восстановить здоровье, заряд, таймеры и подписки. Составьте явный список сбрасываемых данных и проверьте его вторым запуском сцены."},{"id":"checks","title":"Проверка результата","body":"- Один запрос рывка расходует ровно один заряд.\n- Смерть прерывает рывок и запрещает новые действия.\n- Позднее событие анимации не меняет состояние погибшего персонажа.\n- Каждая строка таблицы проверяется отдельно.\n- Недопустимые события имеют определённый результат: игнорирование или диагностическое сообщение.\n- Рестарт возвращает полный начальный набор данных."},{"id":"agent-task","title":"Задание для ИИ-агента","body":"> Найди флаги, управляющие рывком и смертью. Сначала составь таблицу допустимых переходов и приоритет одновременных событий. Затем предложи небольшой автомат без общего фреймворка. Проверь двойное нажатие, смерть в середине рывка, запоздалый callback и повторный старт. Объясни, какие данные автомат хранит отдельно от состояния."}],"relations":[{"type":"requires","direction":"outgoing","label":"Перед началом","key":"lesson/input-and-feedback","title":"Ввод и обратная связь","url":"https://gamemechanics.pro/ru/lessons/input-and-feedback/","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/ru/lesson/input-and-feedback.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/ru/lesson/input-and-feedback.md"},{"type":"requires","direction":"incoming","label":"Продолжить изучение","key":"lesson/agent-workflow","title":"Разработка игры с ИИ-агентом","url":"https://gamemechanics.pro/ru/lessons/agent-workflow/","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/ru/lesson/agent-workflow.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/ru/lesson/agent-workflow.md"},{"type":"requires","direction":"incoming","label":"Продолжить изучение","key":"lesson/npc-perception","title":"Восприятие NPC и память о цели","url":"https://gamemechanics.pro/ru/lessons/npc-perception/","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/ru/lesson/npc-perception.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/ru/lesson/npc-perception.md"},{"type":"requires","direction":"incoming","label":"Продолжить изучение","key":"lesson/gameplay-acceptance","title":"Проверка игрового поведения","url":"https://gamemechanics.pro/ru/lessons/gameplay-acceptance/","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/ru/lesson/gameplay-acceptance.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/ru/lesson/gameplay-acceptance.md"},{"type":"explains","direction":"outgoing","label":"Применение принципа","key":"mechanic/boss-fight-atlas--boss-wind-up","title":"Подготовка","url":"https://gamemechanics.pro/ru/knowledge/mechanic/boss-fight-atlas--boss-wind-up/","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/ru/mechanic/boss-fight-atlas--boss-wind-up.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/ru/mechanic/boss-fight-atlas--boss-wind-up.md"},{"type":"related","direction":"incoming","label":"Связанные материалы","key":"lesson/gameplay-acceptance","title":"Проверка игрового поведения","url":"https://gamemechanics.pro/ru/lessons/gameplay-acceptance/","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/ru/lesson/gameplay-acceptance.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/ru/lesson/gameplay-acceptance.md"},{"type":"related","direction":"incoming","label":"Связанные материалы","key":"lesson/npc-perception","title":"Восприятие NPC и память о цели","url":"https://gamemechanics.pro/ru/lessons/npc-perception/","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/ru/lesson/npc-perception.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/ru/lesson/npc-perception.md"},{"type":"contains","direction":"incoming","label":"Учебные маршруты","key":"path/first-prototype","title":"Первый проверяемый прототип","url":"https://gamemechanics.pro/ru/learn/first-prototype/","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/ru/path/first-prototype.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/ru/path/first-prototype.md"},{"type":"requires","direction":"incoming","label":"Продолжить изучение","key":"path/npc-behavior","title":"Поведение NPC","url":"https://gamemechanics.pro/ru/learn/npc-behavior/","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/ru/path/npc-behavior.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/ru/path/npc-behavior.md"},{"type":"requires","direction":"incoming","label":"Продолжить изучение","key":"path/game-verification","title":"Проверка игры","url":"https://gamemechanics.pro/ru/learn/game-verification/","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/ru/path/game-verification.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/ru/path/game-verification.md"},{"type":"uses","direction":"outgoing","label":"Источники в подборке","key":"resource/state-pattern","title":"Состояния: Game Programming Patterns","url":"https://gamemechanics.pro/ru/resources/#resource-state-pattern","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/ru/resource/state-pattern.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/ru/resource/state-pattern.md"}],"placements":[],"supersededBy":[],"attributes":{"category":"foundations","level":"beginner","minutes":6},"endsAt":null,"provenance":"Авторский материал gamemechanics.pro. Учебные примеры требуют проверки в своём проекте.","license":"CC-BY-4.0","codeLicense":"MIT","attribution":"gamemechanics.pro contributors"}}