From: Game Development Patterns with Unity 2021 (2nd Edition)
By David Baron
Note: This is one of the most important patterns for game architecture!
Also called Publish-Subscribe or Event-Driven programming.
Today's Learning Objectives
What We'll Master
π― Understand tight vs loose coupling
π― Learn Observer Pattern (Subject-Observer)
π― Implement BikeController as Subject
π― Create HUD and Camera as Observers
π― Use C# events and delegates
Goal: Build a bike system where the HUD, camera, and audio
respond to bike events WITHOUT the bike knowing they exist!
The Problem: Tightly Coupled Code
β Without Observer Pattern
public classBikeController : MonoBehaviour
{
public HUDController hud;
public CameraController camera;
public AudioController audio;
voidTakeDamage(float amount)
{
health -= amount;
// TIGHT COUPLING - BikeController knows about everyone!
hud.UpdateHealthBar(health);
camera.ShakeCamera();
audio.PlayDamageSound();
// What if we add more systems? Have to modify this class!
}
}
Problems: Hard dependencies, can't add features without modifying BikeController,
difficult to test, breaks Open/Closed Principle
β οΈ Maintainability Nightmare: Adding a new feature (like particle effects on damage)
requires editing BikeController, creating merge conflicts and breaking existing code!
What is the Observer Pattern?
Observer Pattern
A behavioral pattern that defines a one-to-many dependency
between objects so that when one object changes state,
all its dependents are notified automatically.
In Simple Terms: Like a newsletter - the publisher (Subject) sends updates,
subscribers (Observers) receive them. The publisher doesn't need to know who subscribes!
Observer Pattern: Benefits & Drawbacks
β Benefits
Loose Coupling: Subject doesn't know observers
Extensibility: Add observers without modifying subject
Dynamic: Subscribe/unsubscribe at runtime
Open/Closed: Open for extension, closed for modification
Testability: Mock observers easily
β οΈ Drawbacks
Memory Leaks: Forgetting to unsubscribe
Unpredictable Order: Observer execution order undefined
Debugging: Hard to trace event flow
Performance: Many observers = overhead
Cascading Updates: Chain reactions possible
Golden Rule: Always unsubscribe in OnDestroy() to avoid memory leaks!
Add Cube GameObject (the bike) with BikeController
Add Camera with CameraController
Add empty GameObject "HUD" with HUDController
Add empty GameObject "Audio" with AudioController
Create TestClient script to trigger events (next slide)
Run and press keys to test!
Expected Result: When BikeController takes damage, console shows
messages from HUD, Camera, AND Audio - all responding to the same event!
Implementation Step 5: Test Client
using UnityEngine;
namespace Chapter.Observer
{
public classTestClient : MonoBehaviour
{
private BikeController _bikeController;
voidStart()
{
_bikeController = FindObjectOfType<BikeController>();
}
voidUpdate()
{
// Press D to take damageif (Input.GetKeyDown(KeyCode.D))
{
_bikeController.TakeDamage(25f);
}
// Press T to toggle turboif (Input.GetKeyDown(KeyCode.T))
{
if (_bikeController.isTurboActive)
_bikeController.DeactivateTurbo();
else
_bikeController.ActivateTurbo();
}
}
voidOnGUI()
{
GUILayout.Label("Press D: Take Damage");
GUILayout.Label("Press T: Toggle Turbo");
GUILayout.Label($"Health: {_bikeController.health:F0}");
GUILayout.Label($"Turbo: {(_bikeController.isTurboActive ? \"ON\" : \"OFF\")}");
}
}
}
Critical: Preventing Memory Leaks
β οΈ The #1 Observer Pattern Bug
Forgetting to unsubscribe! When an observer is destroyed,
but doesn't unsubscribe, the subject keeps a reference to it - memory leak!
β Without Unsubscribe
voidStart()
{
bike.OnDamage += HandleDamage;
}
// Observer destroyed// bike still has reference!// MEMORY LEAK!
β With Unsubscribe
voidOnDestroy()
{
if (bike != null)
{
bike.OnDamage -= HandleDamage;
}
}
// Clean unsubscribe!// No memory leak!
Best Practice: Always pair += in Start() with -= in OnDestroy()!
Alternative: Unity Events
using UnityEngine;
using UnityEngine.Events;
public classBikeController : MonoBehaviour
{
// UnityEvent shows up in Inspector!public UnityEvent<float> OnDamage;
public UnityEvent OnTurboStart;
voidStart()
{
// Initialize events
OnDamage = new UnityEvent<float>();
OnTurboStart = new UnityEvent();
}
public voidTakeDamage(float amount)
{
health -= amount;
OnDamage?.Invoke(amount);
}
}
UnityEvent Benefits
β Visible in Unity Inspector
β Wire up listeners without code
β Great for designers
β οΈ Slower than C# events (reflection overhead)
Hands-On Implementation π»
40-Minute Implementation Challenge
Implement the Observer Pattern:
Create BikeController with OnDamage and OnTurboStart events
Create HUDController observer (subscribes to both events)
Create CameraController observer (subscribes to OnDamage)
Create AudioController observer (subscribes to both events)
Implement proper unsubscribe in OnDestroy()
Create TestClient to trigger events via keyboard
Test and verify all observers respond
Observer Pattern vs Event Bus
Observer Pattern (Direct)
Observers know about the Subject
bike.OnDamage += HandleDamage;
Use when: 1-to-N relationship, observers know their subject