Decoupling Systems Using C# Actions and Delegates
In this lesson, you will learn how to notify multiple game systems of state changes using an event-driven architecture, effectively cutting the "cables" between your scripts.
The Problem with Direct References
Up until now, if your Player script wanted to tell the UIManager to update the health bar, you likely used a direct reference (e.g., public UIManager ui;). This creates a tight dependency; if you remove the UIManager, your Player script breaks. As your project grows, these dependencies become a tangled web that is difficult to debug.
The Solution: C# Actions
Instead of the Player knowing who is listening, it simply "shouts" into the void: "Health changed!" Any object interested in that shout (like the UIManager or a SoundManager) can subscribe to it. We use the System.Action delegate to represent this notification.
Here is how you implement it:
1. The Publisher (Player.cs)
using System;
using UnityEngine;
public class Player : MonoBehaviour
{
// Define the event
public static event Action OnHealthChanged;
public void TakeDamage()
{
// Trigger the event
OnHealthChanged?.Invoke();
}
}
2. The Subscriber (UIManager.cs)
using UnityEngine;
public class UIManager : MonoBehaviour
{
private void OnEnable() => Player.OnHealthChanged += UpdateUI;
private void OnDisable() => Player.OnHealthChanged -= UpdateUI;
private void UpdateUI()
{
Debug.Log("UI Updated: Health bar redrawn.");
}
}
Why This Works
The Player script has zero knowledge of the UIManager. It doesn't care if one, ten, or zero scripts are listening. This is "decoupling." You can add a SoundManager or a VFXController later, and you won't have to change a single line of code in your Player script.
By using OnEnable and OnDisable to subscribe and unsubscribe, you ensure that your code remains memory-safe and that you aren't trying to send events to objects that no longer exist in the scene.
Key Takeaway: Use C# Actions to let objects broadcast messages without needing to know which other scripts are listening, keeping your codebase clean and modular.