{"schemaVersion":1,"record":{"key":"lesson/state-machines","type":"lesson","id":"state-machines","lang":"en","title":"States and transitions","summary":"Define legal character states, event priority, and transition checks without a growing tangle of flags.","updatedAt":"2026-10-20","status":"active","url":"https://gamemechanics.pro/en/lessons/state-machines/","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/en/lesson/state-machines.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/en/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":"Prerequisites","body":"You need conditions, functions, and a simple enumeration. Use a character that can move, dash, and die. The useful outcome is a transition table that explains which actions are allowed at each moment."},{"id":"principle","title":"The principle","body":"Flags such as `isDashing`, `isDead`, and `canMove` can contradict each other. A finite state machine keeps one state for a chosen system and changes it through explicit events. Transitions may have guards, while entering and leaving a state can trigger one-time actions. Robert Nystrom explains the underlying pattern in [State](https://github.com/munificent/game-programming-patterns/blob/master/book/state.markdown).\n\nDo not put the entire game into one enumeration. Movement and menu visibility may be independent concerns. Start with a boundary: this machine controls the character's available actions. Health, position, and remaining dash duration remain ordinary data.\n\n<span id=\"example-a-short-dash\"></span>"},{"id":"example","title":"Practical example","body":"### A short dash\n\nAssume a dash lasts 0.18 seconds and death interrupts it immediately. These are exercise parameters, not universal recommendations for responsive controls.\n\n| Current state    | Event and guard                  | Next state                 |\n| ---------------- | -------------------------------- | -------------------------- |\n| ready            | dashPressed and charge available | dashing                    |\n| dashing          | dash time expires                | ready                      |\n| ready or dashing | health reaches zero              | dead                       |\n| dead             | restart                          | ready after resetting data |\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` does nothing when the requested state already matches the current state. For a real transition, it runs exit logic, changes state, and then runs entry logic. Spend a charge when entering `dashing`, rather than on every frame. You can also capture the dash direction at that point.\n\nNotice the ordering: death is checked first and ends the update. This gives simultaneous events a defined priority. A different game may choose another ordering, but the decision should be written down."},{"id":"mistakes","title":"Common mistakes","body":"An animation callback, a function run after an animation event, must not accidentally revive a dead character by reporting that a dash has finished. Accept an event only in a compatible state. For delayed callbacks, checking the identity of the action that created them can also be useful.\n\nAvoid building a framework for dozens of imagined future states. Start with an enumeration and a small transition function. If the table fills up with combinations such as running-with-weapon and running-without-weapon, check whether independent systems have been combined unnecessarily.\n\nAssigning `ready` is not a complete restart. Health, charges, timers, and subscriptions must return to their intended initial values too. List that reset data explicitly and exercise the scene a second time."},{"id":"checks","title":"Verify it","body":"- A single dash request spends exactly one charge.\n- Death interrupts a dash and prevents new actions.\n- A late animation event cannot change a dead character's state.\n- Every row of the transition table has a focused check.\n- Illegal events have a defined outcome, such as being ignored or logged.\n- Restarting restores the full initial data set."},{"id":"agent-task","title":"Prompt for an AI agent","body":"> Find the flags controlling dash and death. First produce a table of legal transitions and the priority of simultaneous events. Then propose a small state machine without a general framework. Check double presses, death during a dash, a late callback, and a second scene start. Explain which data remains separate from the current state."}],"relations":[{"type":"requires","direction":"outgoing","label":"Before you start","key":"lesson/input-and-feedback","title":"Input and feedback","url":"https://gamemechanics.pro/en/lessons/input-and-feedback/","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/en/lesson/input-and-feedback.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/en/lesson/input-and-feedback.md"},{"type":"requires","direction":"incoming","label":"Continue learning","key":"lesson/agent-workflow","title":"Developing a game with an AI agent","url":"https://gamemechanics.pro/en/lessons/agent-workflow/","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/en/lesson/agent-workflow.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/en/lesson/agent-workflow.md"},{"type":"requires","direction":"incoming","label":"Continue learning","key":"lesson/npc-perception","title":"NPC perception and target memory","url":"https://gamemechanics.pro/en/lessons/npc-perception/","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/en/lesson/npc-perception.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/en/lesson/npc-perception.md"},{"type":"requires","direction":"incoming","label":"Continue learning","key":"lesson/gameplay-acceptance","title":"Verifying gameplay behavior","url":"https://gamemechanics.pro/en/lessons/gameplay-acceptance/","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/en/lesson/gameplay-acceptance.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/en/lesson/gameplay-acceptance.md"},{"type":"explains","direction":"outgoing","label":"Apply the principle","key":"mechanic/boss-fight-atlas--boss-wind-up","title":"Wind-up","url":"https://gamemechanics.pro/en/knowledge/mechanic/boss-fight-atlas--boss-wind-up/","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/en/mechanic/boss-fight-atlas--boss-wind-up.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/en/mechanic/boss-fight-atlas--boss-wind-up.md"},{"type":"related","direction":"incoming","label":"Related materials","key":"lesson/gameplay-acceptance","title":"Verifying gameplay behavior","url":"https://gamemechanics.pro/en/lessons/gameplay-acceptance/","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/en/lesson/gameplay-acceptance.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/en/lesson/gameplay-acceptance.md"},{"type":"related","direction":"incoming","label":"Related materials","key":"lesson/npc-perception","title":"NPC perception and target memory","url":"https://gamemechanics.pro/en/lessons/npc-perception/","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/en/lesson/npc-perception.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/en/lesson/npc-perception.md"},{"type":"contains","direction":"incoming","label":"Learning paths","key":"path/first-prototype","title":"Your first verifiable prototype","url":"https://gamemechanics.pro/en/learn/first-prototype/","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/en/path/first-prototype.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/en/path/first-prototype.md"},{"type":"requires","direction":"incoming","label":"Continue learning","key":"path/npc-behavior","title":"NPC behavior","url":"https://gamemechanics.pro/en/learn/npc-behavior/","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/en/path/npc-behavior.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/en/path/npc-behavior.md"},{"type":"requires","direction":"incoming","label":"Continue learning","key":"path/game-verification","title":"Game verification","url":"https://gamemechanics.pro/en/learn/game-verification/","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/en/path/game-verification.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/en/path/game-verification.md"},{"type":"uses","direction":"outgoing","label":"Sources in the selection","key":"resource/state-pattern","title":"State: Game Programming Patterns","url":"https://gamemechanics.pro/en/resources/#resource-state-pattern","jsonUrl":"https://gamemechanics.pro/api/v1/knowledge/en/resource/state-pattern.json","markdownUrl":"https://gamemechanics.pro/content/knowledge/en/resource/state-pattern.md"}],"placements":[],"supersededBy":[],"attributes":{"category":"foundations","level":"beginner","minutes":6},"endsAt":null,"provenance":"Original gamemechanics.pro material. Check illustrative examples in your own project.","license":"CC-BY-4.0","codeLicense":"MIT","attribution":"gamemechanics.pro contributors"}}