In this lesson, you will learn how to bridge the gap between your encapsulated state machine logic and external global systems without creating tight, messy dependencies.
The Problem of Tight Coupling
When a state needs to know how much gold the player has or whether the game is paused, it is tempting to reference the GameManager or EconomySystem directly inside the state script. However, this violates encapsulation—your state shouldn't need to know how the EconomySystem is implemented, only that it can retrieve specific data. If you change the system later, you’d have to rewrite every state that touches it.
Defining Request Interfaces
To maintain a clean architecture, we define "Request Interfaces." Instead of the state accessing a concrete class, we create an interface that exposes only the necessary data. This allows the state to remain agnostic of the underlying system’s logic.
For example, if your CombatState needs to check if the player is currently invincible, define an interface for the query:
public interface IPlayerStatusProvider {
bool IsInvincible { get; }
}
public class CombatState : IState {
private IPlayerStatusProvider _statusProvider;
public CombatState(IPlayerStatusProvider statusProvider) {
_statusProvider = statusProvider;
}
public void Update() {
if (_statusProvider.IsInvincible) {
// Handle protected logic safely
}
}
}
Implementing Through Dependency Injection
By passing this interface into the state’s constructor (or a setup method), you enforce a strict boundary. The PlayerManager (which implements IPlayerStatusProvider) handles the complex logic of calculating invincibility, while the CombatState simply consumes the result.
This approach prevents your states from "leaking" their responsibilities into your global systems. The state machine remains a pure controller of game logic, and the external systems remain isolated providers of data. If you later decide to move the PlayerManager logic to a ScriptableObject or a different service, you only need to update the binding—your CombatState logic remains completely untouched.
Key Takeaway: By using interfaces to request data, you decouple your states from the implementation details of external systems, ensuring your architecture remains flexible and maintainable as your game grows.