How to Build a State Machine for Your Game (Complete Guide)

After building five games with increasingly spaghetti-like code, I finally understood why every experienced developer kept telling me to “just use a state machine.” This guide covers everything you need to implement one properly.

Table of Contents

Quick Answer

  • A state machine organizes game logic into discrete states (Idle, Walk, Jump, Attack) where only one state is active at a time
  • Each state has Enter(), Update(), and Exit() methods for clean setup, execution, and cleanup
  • Transitions are triggered by events (input, collision, timers) and switch between states
  • Benefits: Eliminates nested if-statements, makes adding behaviors easy, and improves debugging
  • Start simple with 3-4 states, draw your diagram first, and expand as needed

What Is a State Machine and Why Do You Need One?

After building five games with increasingly spaghetti-like code, I finally understood why every experienced developer kept telling me to “just use a state machine.” Now I can’t imagine building a game without one.

A state machine (or finite state machine, FSM) is a design pattern that organizes your game logic into discrete states—like Idle, Walking, Jumping, or Attacking—where only one state can be active at a time. Instead of drowning in nested if-statements checking whether your player is grounded, attacking, stunned, AND holding a weapon, you simply ask: “What state am I in? Run that state’s code.”

This tutorial will walk you through building a practical state machine from scratch, applicable to any game engine. By the end, you’ll have a reusable system that makes adding new behaviors trivial instead of terrifying.

Core Concepts: States, Transitions, and Events

Every state machine has three essential components:

States: The distinct modes your entity can be in. A player character might have states like Idle, Walk, Run, Jump, Fall, Attack, and Hurt. Each state contains its own logic and knows nothing about other states.

Transitions: The rules that determine when to switch from one state to another. For example: “If the player presses Jump while in the Idle state, transition to the Jump state.” Transitions can be triggered by input, timers, collision events, or any game condition.

Events: Actions that trigger transitions. These could be player inputs (press jump), physics events (hit ground), or gameplay triggers (take damage). Events are the “why” behind state changes.

The golden rule: only one state is active at any time. When a transition occurs, the current state exits, then the new state enters. This enforces clean separation and prevents conflicting behaviors.

Basic Implementation: The Code Structure

Let’s build a state machine step by step. I’ll use pseudocode that translates easily to C#, GDScript, C++, or any language.

Step 1: Define the State Interface

Every state needs three core methods:

interface IState {
    void Enter();      // Called when entering this state
    void Update(dt);   // Called every frame while in this state
    void Exit();       // Called when leaving this state
}

The Enter() method handles setup—playing animations, resetting timers, enabling hitboxes. Update() runs the state’s main logic every frame. Exit() handles cleanup—stopping sounds, disabling effects.

Step 2: Create the State Machine Controller

class StateMachine {
    IState currentState;
    
    void ChangeState(IState newState) {
        if (currentState != null) {
            currentState.Exit();
        }
        currentState = newState;
        currentState.Enter();
    }
    
    void Update(dt) {
        if (currentState != null) {
            currentState.Update(dt);
        }
    }
}

This controller handles the transition logic. When ChangeState() is called, it properly exits the old state before entering the new one. The Update() method simply delegates to the current state.

Step 3: Implement Concrete States

Here’s a practical example for a platformer character:

class IdleState implements IState {
    Player player;
    StateMachine fsm;
    
    void Enter() {
        player.PlayAnimation("idle");
        player.velocity.x = 0;
    }
    
    void Update(dt) {
        // Check for transitions
        if (Input.IsPressed("jump") && player.IsGrounded()) {
            fsm.ChangeState(new JumpState(player, fsm));
        }
        else if (Input.GetAxis("horizontal") != 0) {
            fsm.ChangeState(new WalkState(player, fsm));
        }
    }
    
    void Exit() {
        // Nothing to clean up for idle
    }
}

Each state is self-contained. The IdleState knows when to transition to Jump or Walk, but it doesn’t know anything about how those states work internally.

Transition Patterns: Who Decides When to Switch?

There are two common approaches to handling transitions:

States Decide (Push Model): Each state checks conditions and calls ChangeState() when appropriate. This is what we showed above. It’s intuitive but can lead to states knowing too much about each other.

Controller Decides (Pull Model): States return a “suggested next state” or transition signal, and the controller makes the final decision. This centralizes transition logic but can become a massive switch statement.

For most games, the Push Model works well. States handle their own transitions, and the controller stays simple. If you find yourself with complex inter-state dependencies, consider a hybrid approach where common transitions (like “take damage” leading to Hurt state) are handled at the controller level.

Practical Example: Complete Player Controller

Let’s build a complete example with Idle, Walk, Jump, and Fall states:

// State factory for cleaner transitions
class PlayerStates {
    static IdleState Idle(Player p, StateMachine sm) => new IdleState(p, sm);
    static WalkState Walk(Player p, StateMachine sm) => new WalkState(p, sm);
    static JumpState Jump(Player p, StateMachine sm) => new JumpState(p, sm);
    static FallState Fall(Player p, StateMachine sm) => new FallState(p, sm);
}

class JumpState implements IState {
    void Enter() {
        player.velocity.y = player.jumpForce;
        player.PlayAnimation("jump");
    }
    
    void Update(dt) {
        // Apply horizontal movement
        player.velocity.x = Input.GetAxis("horizontal") * player.airSpeed;
        
        // Transition to fall when ascending stops
        if (player.velocity.y <= 0) {
            fsm.ChangeState(PlayerStates.Fall(player, fsm));
        }
    }
    
    void Exit() { }
}

class FallState implements IState {
    void Enter() {
        player.PlayAnimation("fall");
    }
    
    void Update(dt) {
        player.velocity.x = Input.GetAxis("horizontal") * player.airSpeed;
        
        // Land when grounded
        if (player.IsGrounded()) {
            if (Input.GetAxis("horizontal") != 0) {
                fsm.ChangeState(PlayerStates.Walk(player, fsm));
            } else {
                fsm.ChangeState(PlayerStates.Idle(player, fsm));
            }
        }
    }
    
    void Exit() { }
}

Notice how each state only cares about its own behavior and the conditions for leaving. Adding a new state like DoubleJump means creating a new class and updating JumpState's transition logic—no massive refactoring required.

Advanced Techniques: Hierarchical and Pushdown State Machines

Once you're comfortable with basic FSMs, two advanced patterns solve common problems:

Hierarchical State Machines (HSM): States can contain sub-states. A "Combat" super-state might contain "Attack," "Block," and "Dodge" sub-states. This reduces code duplication when multiple states share behaviors (like "always take damage regardless of sub-state").

Pushdown Automata: Instead of replacing the current state, new states are pushed onto a stack. When they complete, they pop off, returning to the previous state. Perfect for interruptions like pausing, cutscenes, or stun effects that should return to the previous action.

// Pushdown example
class StateMachine {
    Stack<IState> stateStack;
    
    void PushState(IState state) {
        if (stateStack.Count > 0) {
            stateStack.Peek().Pause();
        }
        stateStack.Push(state);
        state.Enter();
    }
    
    void PopState() {
        stateStack.Pop().Exit();
        if (stateStack.Count > 0) {
            stateStack.Peek().Resume();
        }
    }
}

Common Mistakes to Avoid

Too Many States: Don't create a state for every animation. "WalkLeft" and "WalkRight" should be one "Walk" state with a direction parameter. States represent behavioral modes, not visual variations.

Forgotten Exit Cleanup: If Enter() starts a particle effect, Exit() must stop it. Test state transitions thoroughly—interrupted states often reveal missing cleanup.

Transition Spaghetti: If every state can transition to every other state, your FSM becomes as messy as the if-statements it replaced. Sketch your state diagram on paper first. Most states should only transition to 2-4 other states.

Skipping the Diagram: Always draw your state diagram before coding. A visual map of states and transitions will catch design problems early and serve as documentation.

Pro Tips

  • Start simple: Begin with 3-4 states, then expand. A working small FSM teaches more than a complex broken one.
  • Use enums for state identification: Even with a class-based system, having an enum for state types simplifies debugging and serialization.
  • Add state history for debugging: Log state transitions with timestamps. When something breaks, you'll have a timeline of exactly what happened.
  • Consider state duration timers: Add a built-in timer to your state base class. Many states need "minimum time before transition" logic.
  • Separate physics from state logic: States should set velocities and flags; let your physics system handle actual movement. This prevents frame-rate dependent behavior.
  • Test transitions, not just states: The bugs live in the seams between states. Spam state changes during playtesting.

Frequently Asked Questions

State Machine vs Behavior Tree: Which Should I Use?

State machines excel when entities have distinct modes with clear transitions—player characters, UI flows, game phases. Behavior trees work better for complex AI with many interruptible decisions. For player controllers, state machines are almost always the right choice. For enemy AI, consider starting with an FSM and graduating to behavior trees if it gets unwieldy.

Should Each Enemy Have Its Own State Machine?

Yes, each instance needs its own state machine, but they can share state class definitions. Create state classes once, instantiate state machines per entity. This keeps memory reasonable while allowing independent behavior.

How Do I Handle Animations with State Machines?

Call PlayAnimation() in Enter() for each state. Your animation system should handle blending. Don't tie state transitions to animation completion unless necessary—it creates rigid, unresponsive gameplay. Instead, use minimum state durations.

Can I Have Multiple State Machines Per Entity?

Absolutely. A character might have separate state machines for movement, combat, and inventory. This prevents explosion of combined states like "Walking_Attacking_InventoryOpen." Each machine handles one concern.

My State Machine Has 20+ States. Is That Too Many?

Probably. Look for opportunities to consolidate similar states or introduce hierarchy. If you have "AttackSword," "AttackAxe," "AttackSpear," that's likely one "Attack" state with a weapon parameter. Twenty distinct behavioral modes, however, might be legitimate.

Summary

State machines transform chaotic game logic into organized, maintainable code. By thinking in terms of discrete states with explicit transitions, you make your codebase easier to understand, debug, and extend. Start with the basic Enter/Update/Exit pattern, draw your state diagram before coding, and expand to hierarchical or pushdown patterns when simple FSMs aren't enough.

Your future self—staring at code months later wondering why the player gets stuck in a wall—will thank you for taking the time to build a proper state machine today.

Related posts