In this lesson, you will learn how to create a centralized message hub that allows disparate systems to communicate without needing direct references to one another.
The Problem of Tight Coupling
In our previous work, we’ve used the Service Locator and Dependency Injection to manage object relationships. However, if your PlayerHealth needs to tell the UIManager, AudioSystem, and VFXManager that the player died, you risk creating a "dependency web." Each manager ends up knowing about the others, making your code rigid and difficult to refactor.
The Centralized Event Bus
A Centralized Event Bus acts as a middleman. Instead of the PlayerHealth calling three different managers, it simply publishes a PlayerDiedEvent to a central hub. Any system interested in that event—whether it's the UI, the sound engine, or the particle system—simply "subscribes" to the hub. They don't know who sent the message, and the sender doesn't know who is listening.
This is often implemented using a static C# Action or a ScriptableObject that holds a collection of events. Here is a simplified implementation:
public static class GameEvents
{
public static event Action OnPlayerDied;
public static void TriggerPlayerDied()
{
OnPlayerDied?.Invoke();
}
}
Implementation Logic
- The Publisher: Inside your
PlayerHealth, you callGameEvents.TriggerPlayerDied()when the health reaches zero. - The Subscriber: Inside your
UIManager, you subscribe inOnEnableand unsubscribe inOnDisableto prevent memory leaks:
void OnEnable() => GameEvents.OnPlayerDied += UpdateGameOverUI;
void OnDisable() => GameEvents.OnPlayerDied -= UpdateGameOverUI;
private void UpdateGameOverUI() { /* Show menu */ }
By decoupling these systems, you can remove the UIManager entirely, and the PlayerHealth script won't throw a single null reference error. This architecture allows you to scale your project by adding new features—like a QuestManager that tracks deaths—without ever touching the original character code.
Key Takeaway: An Event Bus creates a "fire-and-forget" communication channel that eliminates direct dependencies between your game systems, ensuring your architecture remains modular and highly scalable.