In this lesson, you will learn how to identify hardcoded references in your prefabs and refactor them into a clean, event-driven architecture using ScriptableObjects.
The Problem: Tight Coupling
When a prefab directly references another script—like a PlayerController talking directly to a UIManager—you create "Tight Coupling." If you delete the UIManager from your scene, the PlayerController throws a NullReferenceException. This makes your prefabs fragile and hard to move between scenes.
The Solution: Decoupling with ScriptableObjects
Instead of the PlayerController knowing about the UIManager, both should communicate through a third party: a ScriptableObject Event Channel.
- Create the Event Channel: Create a
ScriptableObjectthat holds a C#Action. - The Publisher: The
PlayerControllerraises the event on the asset. - The Subscriber: The
UIManagerlistens to the same asset.
Here is how you refactor a direct call into an event:
Before (Tight Coupling):
public class PlayerController : MonoBehaviour {
public UIManager uiManager; // Direct dependency!
void TakeDamage() {
uiManager.UpdateHealthBar(); // Fragile reference
}
}
After (Decoupled):
public class PlayerController : MonoBehaviour {
public VoidEventChannel healthChangedEvent; // ScriptableObject asset
void TakeDamage() {
healthChangedEvent.Raise(); // No knowledge of who is listening
}
}
Why This Works
The PlayerController no longer cares if a UIManager exists. You could have zero, one, or ten systems listening to that healthChangedEvent asset. Because the reference is now an asset in your Project folder rather than an object in your Hierarchy, you can safely swap, delete, or re-organize your scenes without breaking your code connections. This is the foundation of building a flexible, professional game architecture where components communicate without needing to know each other's existence.
Key Takeaway: By replacing direct object references with ScriptableObject event channels, you transform brittle, interdependent prefabs into modular, independent components.