Course contentsShow
← Unity Game Development
Lesson 484 of 8287. Advanced Game ArchitectureFree lesson

Injecting Game State into Decoupled Systems

Replace static global references with injected service instances to make gameplay systems more modular and easier to maintain or refactor.

In this lesson, you’ll learn how to break free from the constraints of static global variables by injecting live game state directly into your decoupled systems.

The Problem with Statics

In earlier lessons, we relied on static classes (like GameState.Instance) to access data across our project. While convenient, this creates "hard-coded" dependencies. If your PlayerHealth system directly references a static GameManager, you cannot easily test that system in isolation or swap out the manager for a different implementation (e.g., for a tutorial versus a main level).

Injecting State via Interfaces

To decouple your systems, we shift from "finding" global data to "receiving" it. Instead of a class looking up the state, we pass the state instance into the class at the moment of creation (or through an initialization method).

Consider an EnemyAI that needs to know if the game is paused. Rather than checking a static GlobalState, we define a IGameState interface and inject it:

public interface IGameState {
    bool IsPaused { get; }
}

public class EnemyAI : MonoBehaviour {
    private IGameState _gameState;

    // We inject the state dependency here
    public void Construct(IGameState gameState) {
        _gameState = gameState;
    }

    void Update() {
        if (_gameState.IsPaused) return;
        // Perform logic...
    }
}

Why This Matters

By using an interface, your EnemyAI no longer knows (or cares) where the state comes from. During gameplay, you might inject a ProductionGameState that reads from your real save data. During unit testing, you can inject a MockGameState that allows you to toggle IsPaused manually without launching the whole game.

This approach transforms your architecture from a rigid web of static references into a flexible collection of independent modules. Because these systems now explicitly declare their dependencies, your codebase becomes significantly easier to refactor, as you can clearly see exactly what information is required to make a system function.

Key Takeaway: Stop letting systems hunt for global data; instead, explicitly inject required states through constructors or interfaces to ensure your systems remain truly decoupled and testable.