State Pattern
Summary
The State pattern is the Gang of Four design pattern whose intent is to “allow an object to alter its behavior when its internal state changes. The object will appear to change its class” (Nystrom, Game Programming Patterns, “State” chapter, quoting the Gang of Four). Instead of one script that checks a mode variable everywhere, each state (idle, walking, jumping, stunned, paused) becomes its own small class with the same interface, and a state machine object delegates to whichever state is current.
Unity’s e-book introduces the pattern with a playable character: standing still, walking when the stick moves, leaping when the jump button is pressed, and returning to idle on landing. It frames the pattern as solving two problems: an object should change its behaviour when its internal state changes, and “state-specific behavior is defined independently”, so adding new states does not affect existing ones (Unity Technologies, Level Up Your Code with Game Programming Patterns, see source-unity-game-programming-patterns).
How this page relates to ai-state-machine-pattern. Both pages describe the same underlying pattern. This page covers the general form for player characters and game objects (controllers, doors, weapons, game flow), with input, physics and a state-change event. The AI page applies it to NPC decision-making (idle, attack, flee), compares it with Behaviour Trees, and is the place to go for enemy brains. Unity’s Animator Controller is a third, separate thing: a state machine for animation clips, which pairs well with this pattern but should not hold gameplay logic (see unity-animator-controller).
Implementation
The problem: an enum and a switch
The naive approach stores the current state in an enum and switches on it every frame. Unity’s e-book shows this as UnrefactoredPlayerController and notes that it “would work, but the PlayerController script can get messy quickly. Adding more states and complexity requires us to revisit the PlayerController script’s internals each time” (Unity Technologies, Level Up Your Code with Game Programming Patterns, see source-unity-game-programming-patterns).
public enum PlayerControllerState { Idle, Walk, Jump }
private PlayerControllerState state;
private void Update()
{
switch (state)
{
case PlayerControllerState.Idle: Idle(); break;
case PlayerControllerState.Walk: Walk(); break;
case PlayerControllerState.Jump: Jump(); break;
}
}Nystrom traces the same progression in his chapter: a pile of Boolean flags (isJumping, isDucking) allows invalid combinations, an enum fixes that, and the State pattern then fixes the problem that each state’s data and logic are scattered across one large class (Nystrom, Game Programming Patterns, “State” chapter).
The IState interface
Every state implements the same three-step lifecycle.
public interface IState
{
void Enter(); // runs once when the machine switches into this state
void Execute(); // runs every frame while this state is current
void Exit(); // runs once just before the machine leaves this state
}The e-book calls the middle method Update and notes that it is “sometimes called Execute or Tick”; Unity’s public sample project for the e-book renames it Execute, which avoids confusion with MonoBehaviour.Update (Unity Technologies, Level Up Your Code with Game Programming Patterns, see source-unity-game-programming-patterns; Unity Technologies, game-programming-patterns-demo sample repository on GitHub). Enter and Exit are where one-off effects belong: start an animation, play a jump sound, reset a timer, spawn landing dust. (For effects that several states share, such as hit stop and camera shake, have Enter or Exit announce the moment and let a feedback component play it; see “Feel across states” below.) Nystrom points out that entry actions remove duplication, because every transition into a state gets the same setup without each caller repeating it (Nystrom, Game Programming Patterns, “State” chapter).
The StateMachine class
The machine is a plain C# class, not a MonoBehaviour. It tracks the current state, runs the Exit-then-Enter handshake, and raises an event so other systems can react.
public class StateMachine
{
public IState CurrentState { get; private set; }
public event Action<IState> StateChanged;
public void Initialize(IState startingState)
{
CurrentState = startingState;
startingState.Enter();
StateChanged?.Invoke(startingState);
}
public void TransitionTo(IState nextState)
{
if (nextState == null || nextState == CurrentState) return;
CurrentState?.Exit();
CurrentState = nextState;
nextState.Enter();
StateChanged?.Invoke(nextState);
}
public void Execute() => CurrentState?.Execute();
}The Initialize / TransitionTo / Execute shape, the read-only CurrentState, and the stateChanged event all come from Unity’s sample project (Unity Technologies, game-programming-patterns-demo sample repository on GitHub). In the e-book version the machine also stores a field for each concrete state (walkState, jumpState, idleState) and creates them in its constructor (Unity Technologies, Level Up Your Code with Game Programming Patterns, see source-unity-game-programming-patterns). The version above moves those references to the player controller instead, so the same StateMachine class can be reused unchanged for a door, a weapon or a game-flow manager.
A concrete state
Each state receives the object it controls (the context) through its constructor and decides its own transitions.
public class IdleState : IState
{
private readonly PlayerStateController player;
public IdleState(PlayerStateController player) => this.player = player;
public void Enter() => player.SetHorizontalSpeed(0f); // and e.g. animator.Play("Idle")
public void Execute()
{
if (player.JumpPressed || !player.IsGrounded) // jump, or walked off a ledge
{
player.StateMachine.TransitionTo(player.Jump);
return;
}
if (Mathf.Abs(player.MoveInput) > 0.01f)
player.StateMachine.TransitionTo(player.Walk);
}
public void Exit() { }
}Return straight after calling TransitionTo. The machine has already run Exit on this state and Enter on the next, so any code that runs afterwards is acting in a state the object is no longer in.
The context: player controller
The MonoBehaviour reads input, performs the ground check, and asks the machine to run the current state. It contains no switch. It creates one instance of each state in Awake and reuses them, rather than writing new JumpState() on every transition.
private void Awake()
{
rb = GetComponent<Rigidbody2D>();
Idle = new IdleState(this);
Walk = new WalkState(this);
Jump = new JumpState(this);
}
private void Start() => StateMachine.Initialize(Idle);
private void Update()
{
MoveInput = Input.GetAxisRaw("Horizontal");
JumpPressed = Input.GetButtonDown("Jump");
IsGrounded = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, groundLayer);
StateMachine.Execute();
}States only record intentions (SetHorizontalSpeed, RequestJump); the controller applies them to rb.linearVelocity in FixedUpdate, which keeps physics on the fixed timestep (see monobehaviour-lifecycle and unity-rigidbody2d). The e-book suggests exactly this kind of split: the per-frame method can be segmented “as MonoBehaviour does, using a FixedUpdate for physics, LateUpdate, and so on” (Unity Technologies, Level Up Your Code with Game Programming Patterns, see source-unity-game-programming-patterns).
The full, compilable example (interface, machine, controller and all three states) is in PlayerStateController.cs. It targets Unity 6 and uses the old Input Manager to match the other pattern pages; new Unity 6 projects default to the Input System package, so set Active Input Handling to Both or swap in an InputAction (see unity-input-manager-vs-input-system).
Transitions in the example
| From | Condition | To |
|---|---|---|
| Idle | horizontal input | Walk |
| Idle or Walk | jump pressed while grounded | Jump (with upward impulse) |
| Idle or Walk | no longer grounded (walked off a ledge) | Jump (falling, no impulse) |
| Walk | no horizontal input | Idle |
| Jump | grounded again and moving down, after a short minimum air time | Walk or Idle |
JumpState stores the time it was entered so it can ignore the ground check for the first few frames of a jump. Because it holds per-object data, each player needs its own instance. Nystrom draws this distinction between stateless states, which can be shared as a single static instance, and stateful states, which must be instantiated per object (Nystrom, Game Programming Patterns, “State” chapter).
Announcing state changes (Observer)
The e-book recommends adding events so outside objects can respond when a state is entered or exited (Unity Technologies, Level Up Your Code with Game Programming Patterns, see source-unity-game-programming-patterns). With the StateChanged event above, UI, audio or debug views can listen without the states knowing about them. This is the observer-pattern in action; the sample project uses it to recolour the player capsule and update an on-screen label.
public class PlayerStateDebugView : MonoBehaviour
{
[SerializeField] private PlayerStateController player;
[SerializeField] private SpriteRenderer spriteRenderer;
private void OnEnable() => player.StateMachine.StateChanged += OnStateChanged;
private void OnDisable() => player.StateMachine.StateChanged -= OnStateChanged;
private void OnStateChanged(IState state)
{
spriteRenderer.color = state switch
{
IdleState => Color.white,
WalkState => Color.cyan,
JumpState => Color.yellow,
_ => Color.magenta
};
}
}Feel across states
A player machine soon has a dozen states that can all land, hit or get hurt: Jump, Fall, Dash, AttackAir, WallSlide. If each state calls HitStop, the camera shake and the particle spawner itself, the same effect is copied into many classes, the timings drift apart (the landing from a dash shakes harder than the landing from a jump because someone tuned only one of them), and turning juice off for a playtest means editing every state.
The fix is to separate what happened from how it feels. This is this wiki’s recommended structure, built from the observer-pattern and the e-book’s advice to add events to states (Unity Technologies, Level Up Your Code with Game Programming Patterns, see source-unity-game-programming-patterns):
- States raise gameplay events and never call effects.
JumpState.EnterraisesJumped; the moment a fall ends, the state raisesLandedwith the fall speed; an attack state raisesHitLandedwith the damage. The event carries the data the feedback needs (position, speed, damage), not instructions. - One feedback component listens and maps events to effects. A
FeedbackPlayersubscribes inOnEnable, picks a preset for each event (light or heavy hit by damage, no landing feedback below a minimum fall speed) and plays the hit stop, shake, particles and sound. It is the only place that knows aboutTime.timeScale, Cinemachine or particle prefabs. - Tuning lives in ScriptableObject presets. Each preset (
Jump,Land,LightHit,HeavyHit) is an asset with hit-stop seconds, shake force, a particle prefab and a clip, in the spirit of theFeelSettingsasset on game-feel. OneJuice Enabledswitch gives the all-off baseline for A/B playtests. - Shared behaviour goes in a base state or the context, not in copied branches. If every airborne state must raise
Landedthe same way, put that check in anAirborneStatesuperstate (the hierarchical machine below) or a helper on the controller.
// Inside JumpState (or an AirborneState base class)
private float fallSpeed; // set back to 0 in Enter
public void Execute()
{
fallSpeed = Mathf.Max(fallSpeed, -player.Velocity.y); // fastest fall so far
if (!Landed()) return;
player.Events.RaiseLanded(player.transform.position, fallSpeed); // announce, don't play
player.StateMachine.TransitionTo(player.Idle);
}// FeedbackPlayer: the only place that knows how a landing feels
private void OnLanded(Vector3 at, float fallSpeed)
{
if (fallSpeed >= minLandingSpeed) Play(landed, at); // landed is a FeedbackPreset asset
}The complete, compilable version is in feedback-layer: PlayerEvents.cs, FeedbackPreset.cs and FeedbackPlayer.cs, with a guarded hit stop that restores the previous time scale. If the listeners live in another scene (a UI scene, an audio manager), replace the C# events with scriptableobject-event-channels; nothing else changes. For effect timings, see vfx-shape-and-timing.
Extensions
- Hierarchical state machines. When several states share behaviour (a grounded character can jump or duck whether walking or running), put the shared logic in a superstate such as
GroundedStateand inheritWalkStateandRunStatefrom it, overriding only what differs (Unity Technologies, Level Up Your Code with Game Programming Patterns, see source-unity-game-programming-patterns; Nystrom, Game Programming Patterns, “State” chapter). This needs an abstract base class rather than a bare interface (see csharp-inheritance). - Concurrent state machines. If a character can carry a weapon in any movement state, combining both concerns into one machine multiplies the number of states. Nystrom’s fix is “two separate state machines”, one for movement and one for equipment (Nystrom, Game Programming Patterns, “State” chapter).
- Pushdown automata. A plain finite state machine has no memory of where it came from. Replacing the single current state with a stack lets a state such as
FiringorPauseMenube pushed on top and then popped to return to whatever came before (Nystrom, Game Programming Patterns, “State” chapter). - Animation. The e-book observes that the Animator window’s workflow “pairs well with the state pattern”: each clip occupies one state and only one is active at a time (Unity Technologies, Level Up Your Code with Game Programming Patterns, see source-unity-game-programming-patterns). A practical split is to keep gameplay rules in the
IStateclasses and have eachEnterset an Animator parameter or callPlay(see unity-animator-scripting).
Other engines
In Godot the same idea is commonly built with one child Node per state under a state-machine node, using signals for state-change notifications. Unreal Engine offers animation state machines inside Animation Blueprints and the StateTree plugin for general hierarchical state machines; in C++ the pattern can also be written by hand exactly as above.
Worked example: from tutorial to core loop without losing momentum
A common mistake is to build the tutorial as its own scene or a series of text screens, then load the “real” level afterwards. The load, the menu and the reset of camera and music break the player’s rhythm exactly when they have just learned the controls. A game-flow state machine can avoid this.
Structure. The game-flow machine has the usual top-level states (MainMenu, Playing, Paused, GameOver; see unity-gamemanager-pattern). The tutorial is not a sibling of Playing. It is a set of sub-states inside Playing, run by a nested machine (a hierarchical state machine, as in Extensions above):
| Sub-state of Playing | Enter unlocks | Ends when the player… |
|---|---|---|
LearnMove | movement only; a short corridor | has moved a few metres |
LearnJump | jump; a low gap appears | has jumped once |
LearnAttack | attack; a training dummy spawns | has hit the dummy three times |
FreePlay (the core loop) | first enemy wave, combat music layer, score timer | (stays here until GameOver) |
Three rules make it work:
- One new thing per step. Each step’s
Enterenables one ability or opens one area and shows one short prompt. - The exit condition is the player doing the action. A step ends on an event from gameplay (
Jumped,HitLanded, distance moved), never on a timer or a “Next” button. Players who already know the controls clear each step in seconds, and nobody is told to do something they have already done. - The hand-over is a transition, not a load. The last step moves into
FreePlay, whoseEnterstarts the core loop in place: the first wave spawns, the music adds its combat layer, and the camera, input map and player object are untouched. AskipTutorialflag for returning players starts the nested machine straight inFreePlay.
public class TutorialStepState : IState
{
// flow = the GameFlow context; machine = the nested tutorial machine; next = the following step
public void Enter()
{
flow.ResetProgress();
data.onEnter.Invoke(); // unlock exactly one new thing
flow.showPrompt.Invoke(data.prompt);
}
public void Execute()
{
if (Done()) machine.TransitionTo(next); // the player's action ends the step
}
public void Exit() => flow.hidePrompt.Invoke();
}The complete version, with steps configured in the Inspector (prompt, goal, count and an onEnter UnityEvent for unlocking), is GameFlow.cs. It counts actions from the same PlayerEvents used in “Feel across states”.
The design reasoning behind this comes from elsewhere in the wiki rather than from the State pattern sources. Super Mario Bros. World 1-1 teaches through level layout and the player’s own actions, with no separate tutorial (see super-mario-bros); player-guidance covers teaching through the environment and introducing the safe version of a mechanic before the dangerous one; and flow notes that the start of a game is the hardest moment for flow, so a clumsy tutorial can break it before play begins. Arguably, the state machine’s contribution is mechanical: it makes “tutorial” a property of the Playing state rather than a different place, so leaving it costs nothing.
Trade-offs
Use it when:
- An object has three or more distinct modes whose update logic genuinely differs.
- You expect to add states during development (dash, wall-slide, swim, stunned).
- Enter and exit actions matter: animations, sounds, timers, enabling hitboxes.
- You want to test or debug one state in isolation.
Think twice when:
- There are only two or three simple states. Unity’s e-book is explicit that “if you only have a few states to track, the extra structure can be overkill” (Unity Technologies, Level Up Your Code with Game Programming Patterns, see source-unity-game-programming-patterns). A
boolor a small enum switch is easier to read. - Behaviour depends on many overlapping conditions with priorities. Nystrom notes that a finite state machine “isn’t even Turing complete” and that complex AI work tends to “slam face-first into the limitations” of the model, which is why game AI has moved towards behaviour trees and planners (Nystrom, Game Programming Patterns, “State” chapter; see game-ai-agent-design).
| Approach | Adding a state | Where transitions live | Best for |
|---|---|---|---|
| Boolean flags | New flag plus checks everywhere | Scattered | Two modes at most |
Enum + switch | Edit the switch and every helper | One large class | A few simple states |
| State pattern | New class implementing IState | Inside each state | Growing, behaviour-rich objects |
| Animator Controller | New node in the graph | Transition arrows and parameters | Animation, not gameplay logic |
The e-book connects the pattern to SOLID: each state is “relatively small and just tracks the conditions for transitioning into another state”, and new states can be added without affecting existing ones, in keeping with the open-closed principle (Unity Technologies, Level Up Your Code with Game Programming Patterns, see source-unity-game-programming-patterns; see also source-unity-design-patterns-and-solid).
Examples
- Platformer player: Idle, Walk, Jump, Fall, WallSlide, Dash. Each state owns its own movement rules, so tuning the dash cannot break wall-sliding (see platformer-design).
- Interactive door: Closed, Opening, Open, Closing, Locked.
Enteron Opening plays the sound and starts the animation; Locked ignores interaction until a key event arrives. - Game flow: MainMenu, Playing, Paused, GameOver states on a game manager, each enabling the right UI and input map in
Enter(see unity-gamemanager-pattern), with tutorial steps as sub-states of Playing (see the worked example above). - Weapon: Ready, Firing, Reloading, Empty, with a concurrent machine alongside the player’s movement machine.
- Enemy brain: the same pattern used for NPC decisions; see ai-state-machine-pattern.
Related
- ai-state-machine-pattern — the same pattern applied to NPC AI (idle, attack, flee), with Behaviour Tree comparison
- observer-pattern — the
StateChangedevent lets UI, audio and animation react to state changes - scriptableobject-event-channels — cross-scene gameplay events for the feedback layer
- game-feel — what the feedback layer is for; FeelSettings and A/B testing juice
- vfx-shape-and-timing — hit stop, hit flash and effect timing values
- player-guidance — teaching through play, behind the tutorial-to-core-loop example
- unity-gamemanager-pattern — the game-flow manager that owns the top-level states
- command-pattern — another way to turn behaviour into objects; commands capture actions, states capture modes
- unity-animator-controller — Unity’s animation state machine, which pairs with but should not replace gameplay states
- unity-animator-scripting — driving the Animator from state
Entermethods - csharp-interfaces —
IStateis an interface contract - csharp-inheritance — abstract base states for hierarchical state machines
- monobehaviour-lifecycle — why input is read in
Updateand physics applied inFixedUpdate - unity-rigidbody2d —
linearVelocityand 2D physics movement used by the example - model-view-presenter-pattern — the other UI-facing architecture pattern from the same e-book
- source-unity-game-programming-patterns — primary source for the Unity implementation