Most small prototypes start with a single scene and a few booleans that say whether the game is paused, over, or in a menu. That works for a weekend jam, but it falls apart as soon as you add save systems, multiple levels, meta progression, online features, or live events.

Robust game state management is what keeps complex games from turning into a tangle of if statements. In this guide we will walk through practical patterns you can use to organize game state so your project is easier to debug, extend, and hand off to teammates.

We will cover:

  • What “game state” actually means in practice
  • Common failure modes when state is unmanaged
  • Core patterns: simple state machine, hierarchical state machine, and state stacks
  • Where to store state data vs behaviour
  • Tips for implementing state systems in Unity, Unreal, and Godot
  • Testing, debugging, and evolving your architecture as the game grows

What game state really is

You can think of game state in two broad layers:

  • Session state: what is happening right now while the game is running.
  • Persistent state: what survives across sessions, saves, and patches.

Session state examples:

  • Which high level mode the game is in: main menu, gameplay, pause, loading, cutscene, shop.
  • What the current level, wave, or mission is.
  • Status of systems like input, audio, AI, or networking.

Persistent state examples:

  • Player profile, unlocked characters, cosmetics, and progression.
  • Save slots, campaign progress, world state.
  • Meta currencies, achievements, and statistics.

Good game state management gives each of these concerns a clear home and flow. When everything lives in random singletons and static variables, bugs show up as:

  • Menus that think they are in gameplay.
  • Levels that load with stale data from previous sessions.
  • Networked games where clients disagree on what is happening.

The goal of the patterns in this article is not to be academically perfect. The goal is to give you structures that keep complexity under control as content and features grow.

Symptoms that your game state architecture is failing

Before you redesign anything, check whether you are already feeling these pain points:

  • You are afraid to add a new feature because you might break the title screen, pause, or save system.
  • Bugs depend on play order: if you jump straight to a level everything is fine, but if you go through the tutorial first, UI disappears or logic breaks.
  • Debugging requires clicking through many scenes so references are initialized in the right order.
  • Save files become fragile, and a small change to data fields breaks loading for existing players.
  • You frequently add “just one more flag” to global managers and hope you remember to reset it.

If any of those feel familiar, investing in a clearer game state architecture will likely pay for itself quickly.

Pattern 1 - A simple game state machine

For many games, a single high level state machine is enough to tame chaos. You list the modes your game can be in and enforce transitions between them instead of letting any system flip booleans at will.

Common high level states:

  • Boot
  • MainMenu
  • Loading
  • Gameplay
  • Paused
  • GameOver
  • Cutscene

Each state owns:

  • Which UI screens are visible.
  • Which systems are active (input, AI, physics, audio).
  • Which transitions are allowed and what triggers them.

Best practices for a simple state machine:

  • Represent states with an enum or explicit classes, not magic strings.
  • Centralize transitions in one GameStateController instead of having random scripts call each other.
  • Make transitions observable: UI and systems subscribe to state change events rather than polling.
  • Log state transitions in development builds so you can replay what happened when a bug occurs.

This pattern alone often eliminates a whole class of “I pressed pause during a cutscene and everything broke” issues.

Pattern 2 - Hierarchical state machines for layered behaviour

As games grow, one flat state machine can become overloaded. You end up duplicating rules for shared behaviour between similar states, such as:

  • Gameplay + Paused + PhotoMode sharing the same level loaded but different input rules.
  • Lobby + Matchmaking + InMatch states for a networked game.

Hierarchical state machines give you layers:

  • A global state machine for high level mode.
  • Nested sub‑machines for gameplay, UI flows, or character behaviour.

Examples:

  • Global state: Boot → Frontend → InGame → PostGame.
  • InGame sub state machine: Exploring → Combat → Dialogue → Cutscene.
  • Character AI state machine: Idle → Patrol → Chase → Attack → Flee.

Benefits:

  • Shared rules (like input context or camera mode) live in parent states.
  • Child states only implement what is unique to them.
  • Transitions inside one sub‑machine do not need to know the full global graph.

You can implement this by:

  • Letting child states hold a reference to their parent and delegating common events upward.
  • Using a library or plugin that supports hierarchical state machines if your engine has one.
  • Keeping state transitions small and composable rather than building one enormous switch statement.

Pattern 3 - State stacks for menus, overlays, and modes

Some types of game state behave like a stack:

  • You are in gameplay.
  • You open the map.
  • From the map you open a settings menu.
  • You close settings, then the map, and return to gameplay in the reverse order.

If you try to model this with a flat state machine, you either explode the number of states or end up duplicating logic. A state stack solves this cleanly:

  • Only the top of the stack receives input.
  • Lower states are still present and can keep time or play ambient animations.
  • Pushing a new state automatically pauses or adjusts the one beneath it.

Good use cases for stacks:

  • UI screens and modal dialogues.
  • Pause and overlay systems.
  • Temporary modes such as photo mode, inspection cameras, or minigames.

Many engines do not provide this pattern out of the box, but it is straightforward to implement with a list, an enum, and a set of small state handler classes or scripts.

Data vs behaviour - where your game state lives

One of the biggest wins for complex projects is separating what the state is from how the game reacts to it.

Guidelines:

  • Keep pure data in serializable structures: plain C# classes, structs, ScriptableObjects, data assets, or JSON.
  • Let systems observe changes and react, instead of letting them own the data.
  • Design your data structures first: player progression, inventory, world flags, quest stages.

Benefits of separating data and behaviour:

  • You can save and load without dragging in every MonoBehaviour or Actor.
  • You can write tests against data transitions without booting an entire scene.
  • Balancing and content changes touch data files, not gameplay code.

In Unity this might look like a GameState ScriptableObject with references to smaller data blobs (profile, session, options), while runtime systems subscribe to changes. In Unreal you might centralize state in a GameInstance subclass and plain structs, then have subsystems react. In Godot you might use an Autoload singleton that holds data while scenes bind to it in a controlled way.

Scoping your game state - global vs local

Not all state should be global. When everything is reachable from everywhere, any script can mutate important state at any time, which makes bugs hard to trace.

Think in scopes:

  • Global scope: profile, meta progression, unlocked content, player settings.
  • Session scope: current run, level, or match.
  • Scene or level scope: enemies, pickups, local triggers.
  • Entity scope: one character, weapon, or puzzle.

Best practices:

  • Be intentional about promoting state upward.
  • When a local event needs to affect global state, go through a clear interface such as an event bus or service.
  • Avoid giving every script a reference to the same monolithic “GameManager”.

This makes it easier to reset state correctly when loading levels, restarting runs, or jumping into debug scenes.

Implementing state systems in popular engines

You do not need a heavy framework to benefit from these ideas. Here are practical starting points per engine.

Unity

  • Use a GameStateController MonoBehaviour in a bootstrap scene that never unloads.
  • Represent high level states with an enum, and keep transitions in one place.
  • Use ScriptableObjects or plain data classes for persistent state and save files.
  • Use UnityEvents, C# events, or a simple message bus to broadcast state changes to UI and systems.
  • Keep each system (input, audio, spawners, enemy AI) listening for state changes instead of reaching into global state directly.

Unreal Engine

  • Put high level state and save data in GameInstance and dedicated subsystems.
  • Use GameMode or GameState for match‑level rules, and PlayerState for per‑player data.
  • Represent mode changes with a small state machine in GameInstance or a custom Manager Actor.
  • Use delegates and BlueprintAssignable events so UI and actors can respond when the state changes.

Godot

  • Use an autoload singleton (for example GameState.gd) to hold global and session data.
  • Emit signals when state changes, and let scenes connect to those instead of polling.
  • Prefer loading and unloading scenes as “states” rather than stuffing everything into one scene tree.
  • For complex behaviour like character AI, build small state machines as separate scripts that own their own state.

Testing and debugging your game state

A good state architecture makes it easier to reproduce bugs instead of harder.

Practical techniques:

  • Log every state transition in development builds with from → to, plus reason.
  • Add a simple debug UI that shows the current high level state, sub‑states, and important flags.
  • Provide cheats or console commands to jump to common states: specific levels, tutorial complete, all weapons unlocked.
  • Write a few automated tests that simulate common flows such as starting a new game, continuing a save, or returning to the main menu after a match.

When a bug report says “if I quit to main menu from a cutscene and reload, audio doubles”, your logs and test harness should make that easy to reproduce.

Evolving your architecture as the project grows

You rarely design the perfect game state system on day one. What you want instead is architecture that can evolve.

Guidelines as you iterate:

  • Start with a simple enum‑based state machine and clean entry and exit points.
  • When a state grows messy, split responsibilities into sub‑machines or dedicated controllers.
  • Move data out of components into serializable data structures as soon as you need saving or undo.
  • Regularly audit who can write to global or session state and tighten those interfaces.

If you treat state management as part of game design rather than just plumbing, you will:

  • Ship fewer weird edge‑case bugs.
  • Onboard new teammates faster.
  • Spend more time on content and feel, and less on chasing desyncs and ghost flags.

Strong game state management is invisible when it is working, but it is one of the biggest differences between a fragile prototype and a game you can confidently support for years.