How to Manage Game States in Unity: From Chaos to Clean Code

A practical, battle-tested guide to building robust state managers in Unity, from simple enum-based systems to scalable, professional patterns.

How to Manage Game States in Unity: From Chaos to Clean Code Guide by Mayank Grover

Here's the thing - I remember my early days at KIXEYE when I thought I was clever by cramming all my game logic into one massive Update method. Menu handling, gameplay mechanics, pause functionality - everything lived in this monster script that grew longer every day. Then came the bugs. Players could somehow jump while in the menu, take damage on the game-over screen, and the pause button... well, let's just say it had a mind of its own.

Been there? Trust me, you're not alone. Game state management is one of those concepts that separates hobbyist projects from professional games, and today I'm going to show you exactly how to manage game states in Unity the right way.

Why Your Game Needs a State Manager (And Why Mine Crashed Without One)

State Management Concept Diagram

Effective game state management is the backbone of a structured player experience, solving the critical problem of controlling the flow and logic of a game. It addresses the question of how to cleanly separate the different phases of your application, such as the main menu, active gameplay, pause screens, and game-over sequences.

By implementing a state management system, you can create a robust framework that dictates what should be happening at any given moment, what inputs are allowed, and how to transition from one state to another. A simple real-world analogy is a traffic light system; just as the lights dictate whether cars should stop, go, or prepare to stop, a game state manager directs the core logic of your game, ensuring that only the rules of the current state—be it "MainMenu" or "Gameplay"—are active, preventing chaotic and buggy overlaps.

Actually, wait - let me tell you what happens when you don't have this system. I've seen students submit projects where pressing the spacebar in the main menu makes their character jump off-screen. Or worse, where the game over screen still lets enemies move around and attack... nothing. These aren't just embarrassing bugs - they're symptoms of missing state management.

Breaking Down the Game State Vocabulary

Before building a state manager, it's crucial to understand the vocabulary and the different architectural patterns you can use to control your game's flow. Here are the terms you'll encounter:

You know what's funny? I spent months at CMU learning about finite state machines in theory classes, but it wasn't until I was debugging that nightmare KIXEYE project that I truly understood why they matter in practice.

The Foundation: Core Concepts Every Developer Needs

Core Concepts Visualization

To manage game states effectively, you need a few key components working together. These concepts form the foundation of any robust state management system. Let me show you how I approach each one:

Defining States with an Enum

The simplest way to define your game's possible states is with an enum. This creates a clean, readable type for tracking the current state.

C#
// C#
public enum GameState
{
    MainMenu,
    Gameplay,
    Paused,
    GameOver
}

Verified: Unity - Enums

The Singleton Game Manager

A central Game Manager, often implemented as a Singleton, ensures you have a single, globally accessible point of control for managing state. This object persists across scene loads.

C#
// C#
using UnityEngine;

public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }
    public GameState CurrentState { get; private set; }

    void Awake()
    {
        // Singleton pattern implementation
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }
}

Verified: Unity Docs - Object.DontDestroyOnLoad

State Transition Logic

A core function of the Game Manager is to handle the transition from one state to another. This method is where you place the logic that needs to run when a state change occurs.

C#
// C#
public void ChangeState(GameState newState)
{
    if (CurrentState == newState) return;

    CurrentState = newState;
    switch (newState)
    {
        case GameState.MainMenu:
            // Logic for entering the Main Menu
            break;
        case GameState.Gameplay:
            // Logic for starting Gameplay
            break;
        case GameState.Paused:
            // Logic for Pausing the game
            break;
        case GameState.GameOver:
            // Logic for the Game Over sequence
            break;
    }
}

Verified: Unity - Switch Statements

Using SceneManager for Major State Changes

For significant state changes, like moving from the main menu to the game, you'll use Unity's SceneManager to load the appropriate scene.

C#
// C#
using UnityEngine.SceneManagement;

public void LoadGameplayScene()
{
    // Assumes your gameplay scene is named "GameLevel"
    SceneManager.LoadScene("GameLevel");
    ChangeState(GameState.Gameplay);
}

Verified: Unity Docs - SceneManager.LoadScene

I learned this the hard way after trying to cram everything into one scene. Trust me, scene-based state management is a game-changer for organization and performance.

Choosing Your Approach: Simple vs. Scalable

Choosing the right state management architecture depends on your project's complexity. Here's a comparison between a simple Enum-based approach and a more scalable ScriptableObject-based system.

Criteria Approach A: Enum-Based State Machine Approach B: ScriptableObject-Based States
Best For Simple games, prototypes, or projects with a small, fixed number of states where all logic can be centralized in one manager. Complex games with many states, or systems where states need to hold their own data and logic, promoting a more decoupled and scalable architecture.
Performance Very high performance, as it relies on a simple integer comparison and a switch statement with minimal overhead. Negligible performance difference for most cases, but involves asset loading and referencing, which can be slightly slower than a direct enum check.
Complexity Low complexity, as it is easy for beginners to understand and implement within a single GameManager script. Higher complexity, requiring a deeper understanding of ScriptableObjects, asset creation, and event-driven architecture to connect states.
Code Example public enum GameState { Menu, Play };
public GameState state;
void Update() { switch(state) { case GameState.Menu: break; } }
public class GameStateSO : ScriptableObject { ... }
public GameStateSO currentState;
void Update() { currentState.OnUpdate(); }

For most student projects, I recommend starting with the enum approach. You can always refactor later when you understand the patterns better.

What You'll Actually Gain From Proper State Management

Implementing a proper game state management system is not just about organization; it provides tangible advantages that directly improve your game and development process.

This one time at KIXEYE, I watched a senior developer refactor a project by adding proper state management, and the bug reports dropped by 70% overnight. That's when I truly understood the power of this pattern.

Battle-Tested Practices I Learned the Hard Way

Writing a state manager is one thing, but writing a great one involves following professional practices that ensure scalability and robustness. Here are the techniques I use in every project:

Decouple States with Events

Instead of having states directly call functions in other scripts, use C# events or UnityEvents. The GameManager can invoke an event on state change, and other systems (like UI or Audio) can subscribe to it.

C#
// C#
using System;

public class GameManager : MonoBehaviour
{
    public static event Action<GameState> OnGameStateChanged;

    public void ChangeState(GameState newState)
    {
        // ... state change logic ...
        OnGameStateChanged?.Invoke(newState); // Notify subscribers
    }
}

Verified: Microsoft Docs - Events Tutorial

Use Time.timeScale for Pausing

The correct way to pause the gameplay state is by setting Time.timeScale to 0. This freezes all physics and time-based calculations. Remember to set it back to 1 to resume.

C#
// C#
public void PauseGame()
{
    Time.timeScale = 0f;
    ChangeState(GameState.Paused);
}

public void ResumeGame()
{
    Time.timeScale = 1f;
    ChangeState(GameState.Gameplay);
}

Verified: Unity Docs - Time.timeScale

Manage UI Panels from a Central Controller

Instead of having each UI screen manage itself, have a central UIManager that listens to state changes and activates or deactivates the appropriate UI panels.

C#
// C#
public class UIManager : MonoBehaviour
{
    public GameObject mainMenuPanel;
    public GameObject pauseMenuPanel;
    public GameObject gameOverPanel;

    void OnEnable() => GameManager.OnGameStateChanged += HandleGameStateChange;
    void OnDisable() => GameManager.OnGameStateChanged -= HandleGameStateChange;

    private void HandleGameStateChange(GameState state)
    {
        mainMenuPanel.SetActive(state == GameState.MainMenu);
        pauseMenuPanel.SetActive(state == GameState.Paused);
        gameOverPanel.SetActive(state == GameState.GameOver);
    }
}

Verified: Unity Docs - GameObject.SetActive

Took me months to figure out this UI pattern, but now it's my go-to approach for any game with multiple screens.

How the Pros Do It: Real Game Examples

Real-World Game Implementation Showcase

Let me show you how some of my favorite games handle state management - these are perfect examples to study:

The Mechanic in Stardew Valley

The game seamlessly transitions between distinct states: farming on your land, exploring the town, delving into the mines, and navigating menus. Each state has unique controls, UI, and available actions.

The Implementation: A robust game state manager likely uses an enum or a class-based state machine to control these phases. When the player enters a new area (e.g., walking from the farm into town), the GameManager transitions to the "Town" state, which might load a new scene additively and change the ambient music and available NPC interactions.

The Player Experience: This clear separation of states ensures the player is never confused. The UI for the inventory and toolbar might persist, but the context-sensitive actions (tilling soil vs. talking to villagers) are only available in the appropriate state, creating an intuitive and immersive experience.

C#
// C#
void Update()
{
    switch (GameManager.Instance.CurrentState)
    {
        case GameState.Farming:
            HandleFarmingInput();
            break;
        case GameState.Town:
            HandleTownInput();
            break;
    }
}

The Mechanic in Hollow Knight

The game fluidly moves between intense combat/platforming (Gameplay), checking the map (MapScreen), and managing charms (Inventory). The Gameplay state is instantly frozen when the map or inventory is opened.

The Implementation: This is a classic example of using Time.timeScale. When the player presses the map button, the GameManager enters the MapScreen state, sets Time.timeScale = 0f, and enables the map UI canvas. Exiting the map reverses the process, setting Time.timeScale = 1f and disabling the UI.

The Player Experience: The ability to instantly pause the action to check the map provides a crucial strategic layer. The player feels in control and can take a moment to plan their route through the dangerous world without being attacked, which is essential for exploration-heavy games.

C#
// C#
public void OpenMap()
{
    GameManager.Instance.ChangeState(GameState.MapScreen);
    Time.timeScale = 0f;
    mapUI.SetActive(true);
}

The Mechanic in Super Mario Odyssey

The game has a clear hierarchy of states: navigating the world map in the Odyssey ship, exploring a specific kingdom (e.g., Sand Kingdom), and entering a sub-area or "pipe" level.

The Implementation: This structure is likely managed by loading scenes. The Odyssey is a persistent hub scene. Selecting a kingdom unloads the previous kingdom scene (if any) and additively loads the new one. Entering a pipe level might load a smaller, separate scene for that specific challenge.

The Player Experience: This scene-based state management creates vast, distinct worlds that feel completely separate, yet the transition between them is smooth. It allows for optimized performance by only keeping the necessary kingdom's assets in memory, resulting in a seamless and expansive adventure.

C#
// C#
public void TravelToKingdom(string kingdomSceneName)
{
    SceneManager.LoadScene(kingdomSceneName, LoadSceneMode.Single);
}

What I find fascinating about these implementations is how invisible they are to players - that's the mark of truly professional game development.

Building Your First State Manager: Three Complete Blueprints

Let me walk you through three complete implementations, from basic to advanced. I use these exact patterns in my own projects:

Blueprint 1: A Basic Main Menu to Gameplay Flow

Scenario Goal: To create a functional main menu with a "Start Game" button that loads the main gameplay scene and correctly updates the game state.

Unity Editor Setup:

Step-by-Step Code Implementation:

  1. Create the GameManager Script: This script will be our central state controller. It uses the Singleton pattern to be easily accessible and persists across scenes.
    C#
    // GameManager.cs
    using UnityEngine;
    using UnityEngine.SceneManagement;
    using System;
    
    public class GameManager : MonoBehaviour
    {
        public static GameManager Instance { get; private set; }
    
        public enum GameState { MainMenu, Gameplay }
        public GameState CurrentState { get; private set; }
    
        public static event Action<GameState> OnGameStateChanged;
    
        void Awake()
        {
            if (Instance == null)
            {
                Instance = this;
                DontDestroyOnLoad(gameObject);
            }
            else
            {
                Destroy(gameObject);
            }
        }
    
        public void ChangeState(GameState newState)
        {
            CurrentState = newState;
            OnGameStateChanged?.Invoke(newState);
        }
    }
  2. Create a Menu Controller Script: This script will handle the UI logic for the main menu, specifically what happens when the start button is clicked.
    C#
    // MainMenuController.cs
    using UnityEngine;
    using UnityEngine.SceneManagement;
    
    public class MainMenuController : MonoBehaviour
    {
        // This function will be linked to the OnClick() event of the Start Button
        public void StartGame()
        {
            // Load the gameplay scene by its name
            SceneManager.LoadScene("GameLevel");
            GameManager.Instance.ChangeState(GameManager.GameState.Gameplay);
        }
    }

    Verified: Unity Docs - SceneManager

  3. Hook up the UI Button: In the MainMenu scene, select your "Start Game" button. In the Inspector, find the Button component and its On Click () event panel. Drag the GameObject that has your MainMenuController script onto the object field, and from the function dropdown, select MainMenuController > StartGame.

Blueprint 2: Implementing a Pause Menu System

Scenario Goal: To allow the player to pause and resume the game using the "Escape" key, which toggles a pause menu overlay and freezes gameplay.

Unity Editor Setup:

Step-by-Step Code Implementation:

  1. Expand the GameManager: Add Paused to the GameState enum and modify the ChangeState logic.
    C#
    // GameManager.cs (Additions)
    public enum GameState { MainMenu, Gameplay, Paused } // Add Paused state
    
    // ... existing code ...
    
    public void TogglePause()
    {
        if (CurrentState == GameState.Gameplay)
        {
            ChangeState(GameState.Paused);
            Time.timeScale = 0f; // Freeze time
        }
        else if (CurrentState == GameState.Paused)
        {
            ChangeState(GameState.Gameplay);
            Time.timeScale = 1f; // Resume time
        }
    }

    Verified: Unity Docs - Time.timeScale

  2. Create the UIManager Script: This script will listen for input to pause the game and manage the visibility of the pause menu.
    C#
    // UIManager.cs
    using UnityEngine;
    
    public class UIManager : MonoBehaviour
    {
        public GameObject pauseMenuPanel;
    
        void Update()
        {
            // Listen for the Escape key to toggle the pause state
            if (Input.GetKeyDown(KeyCode.Escape))
            {
                GameManager.Instance.TogglePause();
            }
        }
    
        void OnEnable() => GameManager.OnGameStateChanged += HandleGameStateChange;
        void OnDisable() => GameManager.OnGameStateChanged -= HandleGameStateChange;
    
        private void HandleGameStateChange(GameManager.GameState state)
        {
            // Show or hide the pause menu based on the game state
            pauseMenuPanel.SetActive(state == GameManager.GameState.Paused);
        }
    
        // Function for the Resume button's OnClick() event
        public void OnResumeButtonPressed()
        {
            GameManager.Instance.TogglePause();
        }
    
        // Function for the Quit button's OnClick() event
        public void OnQuitButtonPressed()
        {
            Application.Quit();
        }
    }
  3. Link UI and GameObjects: In the GameLevel scene, select the UIManager GameObject. Drag the PauseMenuPanel from the Hierarchy into the pauseMenuPanel public field in the Inspector. Hook up the OnResumeButtonPressed and OnQuitButtonPressed functions to their respective buttons' On Click () events.

Blueprint 3: A Finite State Machine for a Player Character

Scenario Goal: To manage a character's state (e.g., Idle, Walking, Jumping) to control behavior and animations, preventing actions like double-jumping.

Unity Editor Setup:

Step-by-Step Code Implementation:

  1. Define Player States: Create an enum inside the player script to define its possible states. This is a local state machine, separate from the main GameManager.
    C#
    // PlayerController.cs
    using UnityEngine;
    
    public class PlayerController : MonoBehaviour
    {
        public enum PlayerState { Idle, Walking, Jumping }
        private PlayerState currentState;
    
        // 3D Movement
        private CharacterController controller;
        private float speed = 5.0f;
        private float jumpHeight = 1.5f;
        private float gravity = -9.81f;
        private Vector3 playerVelocity;
        private bool isGrounded;
    
        // 2D Movement (alternative)
        private Rigidbody2D rb2D;
        private float moveSpeed2D = 5f;
        private float jumpForce2D = 10f;
    
        void Start()
        {
            // 3D Setup
            controller = GetComponent<CharacterController>();
            // 2D Setup
            rb2D = GetComponent<Rigidbody2D>();
    
            ChangeState(PlayerState.Idle);
        }
    }
  2. Implement State Logic in Update: Use a switch statement in Update to call state-specific logic. This keeps the code organized and ensures only the current state's logic runs.
    C#
    // PlayerController.cs (continued)
    void Update()
    {
        // Only allow player input if the main game state is 'Gameplay'
        if (GameManager.Instance.CurrentState != GameManager.GameState.Gameplay) return;
    
        switch (currentState)
        {
            case PlayerState.Idle:
                HandleIdleState();
                break;
            case PlayerState.Walking:
                HandleWalkingState();
                break;
            case PlayerState.Jumping:
                HandleJumpingState();
                break;
        }
    }
    
    private void ChangeState(PlayerState newState)
    {
        currentState = newState;
    }
  3. Code the Behavior for Each State: Create separate methods for each state's logic. This includes handling transitions between states.
    C#
    // PlayerController.cs (3D Version Logic)
    private void HandleIdleState()
    {
        // Transition to Walking if there's movement input
        float move = Input.GetAxis("Horizontal");
        if (Mathf.Abs(move) > 0)
        {
            ChangeState(PlayerState.Walking);
        }
    
        // Transition to Jumping if jump is pressed and grounded
        if (isGrounded && Input.GetButtonDown("Jump"))
        {
            ChangeState(PlayerState.Jumping);
        }
    }
    
    private void HandleWalkingState()
    {
        isGrounded = controller.isGrounded;
        if (isGrounded && playerVelocity.y < 0) playerVelocity.y = 0f;
    
        Vector3 move = new Vector3(Input.GetAxis("Horizontal"), 0, 0);
        controller.Move(move * Time.deltaTime * speed);
    
        // Apply gravity
        playerVelocity.y += gravity * Time.deltaTime;
        controller.Move(playerVelocity * Time.deltaTime);
    
        // Transition back to Idle if movement stops
        if (move.magnitude == 0) ChangeState(PlayerState.Idle);
    
        // Transition to Jumping
        if (isGrounded && Input.GetButtonDown("Jump"))
        {
            playerVelocity.y += Mathf.Sqrt(jumpHeight * -3.0f * gravity);
            ChangeState(PlayerState.Jumping);
        }
    }
    
    private void HandleJumpingState()
    {
        isGrounded = controller.isGrounded;
        // When the player lands, transition back to Idle or Walking
        if (isGrounded)
        {
            ChangeState(PlayerState.Idle);
        }
    
        // Apply gravity while in the air
        playerVelocity.y += gravity * Time.deltaTime;
        controller.Move(playerVelocity * Time.deltaTime);
    }

    Verified: Unity Docs - CharacterController.isGrounded

I've configured this dozens of times, and here's my go-to setup - these are the exact settings I use when building character controllers from scratch.

Ready to Start Building Your First Game?

Now that you understand how to manage game states in Unity properly, you're ready to apply this knowledge to real projects. State management is just one piece of the game development puzzle, but it's a crucial foundation that will make every other system in your game more robust and maintainable.

If you want to go from these basics to building complete, professional-quality games, I've designed a comprehensive course that takes you through the entire journey. You'll learn not just the theory, but how to implement these concepts in real game projects that you can add to your portfolio.

Start building your first complete game with proper state management →

This course covers everything from basic Unity concepts to advanced game architecture patterns, all taught through hands-on projects. You'll build multiple games while learning industry-standard practices that I use in my own development work.


Key Takeaways

Common Questions

What is game state management in Unity?+

Game state management is a programming pattern that controls the different phases of your game (like menus, gameplay, pause screens) by ensuring only the logic for the current state is active, preventing bugs and creating organized code flow.

How do I manage game states in Unity for beginners?+

Start with an enum to define your states (MainMenu, Gameplay, Paused), create a singleton GameManager script to track the current state, and use switch statements to execute state-specific logic in your Update methods.

What's the difference between enum-based and ScriptableObject-based state management?+

Enum-based systems are simpler and perfect for small games with fixed states, while ScriptableObject-based systems are more scalable and modular, better suited for complex games with many states that need their own data and logic.

When should I use SceneManager.LoadScene() vs staying in one scene?+

Use SceneManager.LoadScene() for major state changes like going from main menu to gameplay, as it provides clean separation and better performance. Stay in one scene for minor states like pause menus or inventory screens.

How do I prevent players from performing actions in the wrong game state?+

Check the current game state before processing input. For example, only allow player movement when CurrentState equals GameState.Gameplay, and ignore input when in menu or pause states.

What is Time.timeScale and when should I use it?+

Time.timeScale controls the speed of time-dependent operations in Unity. Set it to 0f to freeze gameplay (perfect for pause menus) and back to 1f to resume normal speed. It affects physics, animations, and Time.deltaTime.

How do I handle UI panels across different game states?+

Create a central UIManager that subscribes to game state change events and activates/deactivates the appropriate UI panels based on the current state, keeping UI logic separate from game logic.

Why use events for state management instead of direct method calls?+

Events create loose coupling between systems - your GameManager doesn't need to know about every UI panel or audio system. When state changes, subscribed systems automatically respond without tight dependencies.

How do I implement a finite state machine for player characters?+

Create a separate enum for player states (Idle, Walking, Jumping), use switch statements in Update to handle state-specific behavior, and include transition logic that moves between states based on input and conditions.

What are the main benefits of proper game state management?+

Proper state management eliminates logic conflicts, makes code cleaner and more maintainable, enables complex gameplay loops, speeds up development workflow, and prevents embarrassing bugs like jumping in menus or taking damage on game-over screens.