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.