Event Bus Pattern - Managing Game Events
Home
Managing Game Events
The Event Bus Pattern
CSCI 3213 - Game Programming
Spring '26 - Week 4, Class 1
Decoupling game systems with centralized events!
Today's Content
π Based on Chapter 6
Implementing an Event Bus for Managing Race Events
From: Game Development Patterns with Unity 2021 (2nd Edition)
By David Baron
Available: Dulaney Browne Library or major book retailers.
This chapter introduces centralized event management for race systems!
Today's Learning Objectives
What We'll Master
π― Understand the Event Bus Pattern
π― Identify tight coupling problems
π― Implement a centralized event bus
π― Use UnityEvent vs C# events
π― Build race countdown system
Goal: Create a RaceEventBus to manage race countdown, start, stop,
and completion events for Blade Racer.
The Problem: Tight Coupling
Scenario: Race Start Event
When a race starts, multiple systems need to respond:
- UI displays "GO!"
- Audio plays race music
- Timer starts counting
- Obstacles begin spawning
- Camera activates follow mode
β Without Event Bus (Tightly Coupled)
public class RaceManager {
public UIController ui;
public AudioManager audio;
public TimerController timer;
public ObstacleSpawner spawner;
public CameraController camera;
void StartRace () {
ui.ShowStartText();
audio.PlayRaceMusic();
timer.StartTimer();
spawner.BeginSpawning();
camera.FollowPlayer();
}
}
Problems: Hard to maintain, brittle, not scalable!
What is the Event Bus Pattern?
Event Bus Pattern
An architectural pattern that provides a centralized messaging system
for decoupled communication between game components.
β
Key Concept
Publishers send events to a central bus.
Subscribers listen for events they care about.
No direct references between components!
In Simple Terms: Like a radio station - broadcasters send messages,
listeners tune in to what they want to hear. Nobody needs to know each other!
How Event Bus Works
Publishers
β
Event Bus
β
Subscribers
Example: Race Start Event
Publisher: Event Bus: Subscribers:
βββββββββββββββ βββββββββββββββ βββββββββββββββ
β RaceManager β β RaceEventBusβ β HUDControllerβ
β βββRaceStartβββΆβ βββββββββββΆβ (shows GO!) β
βββββββββββββββ β Dictionary β βββββββββββββββ
β of Events β βββββββββββββββ
βββββββββββββββββββββββββΆβ AudioManagerβ
β (play music)β
βββββββββββββββ
βββββββββββββββ
βTimerControl β
β(start timer)β
βββββββββββββββ
One-to-Many: One publisher can notify multiple subscribers with zero coupling!
Event Bus: Benefits & Drawbacks
β
Benefits
Decoupling: Publishers don't know subscribers
Scalability: Easy to add new subscribers
Maintainability: Changes isolated to event bus
Flexibility: Subscribe/unsubscribe dynamically
Clean Code: No dependency chains
β οΈ Drawbacks
Debugging: Hard to trace event flow
Memory Leaks: Forgetting to unsubscribe
Performance: Slight overhead for lookups
Type Safety: Can lose compile-time checks
Overuse: Can become "event soup"
β οΈ Critical: ALWAYS unsubscribe in OnDisable or OnDestroy to prevent memory leaks!
When to Use Event Bus
β
Perfect For
Game state changes (Start, Pause, GameOver)
UI updates (Score changed, Health changed)
Race events (Countdown, Start, Finish)
Achievement unlocks
Player death/respawn
Level completion
β Not Suitable For
High-frequency events (every frame)
Direct component communication
Parent-child relationships
Events needing guaranteed order
Events requiring return values
Simple one-to-one communication
Rule of Thumb: Use Event Bus for infrequent, broadcast-style events
where many systems need to react.
Defining Race Event Types
RaceEventType Enum
Our racing game needs these discrete events:
COUNTDOWN
Timer counting down before race start (3... 2... 1...)
START
Race has officially started - GO!
STOP
Race paused or stopped mid-race
FINISH
Player crossed finish line
RESTART
Reset race to beginning
PAUSE
Game paused (show pause menu)
RaceEventBus Implementation Part 1
using UnityEngine;
using UnityEngine.Events;
using System.Collections.Generic;
namespace Chapter.EventBus
{
public enum RaceEventType
{
COUNTDOWN,
START,
STOP,
FINISH,
RESTART,
PAUSE,
QUIT
}
public static class RaceEventBus
{
private static readonly Dictionary<RaceEventType, UnityEvent>
_eventDictionary = new Dictionary<RaceEventType, UnityEvent>();
public static void Subscribe (RaceEventType eventType, UnityAction listener)
{
if (!_eventDictionary.ContainsKey(eventType))
{
_eventDictionary[eventType] = new UnityEvent();
}
_eventDictionary[eventType].AddListener(listener);
}
Key Points: Static class (no instantiation), Dictionary stores UnityEvents,
Subscribe auto-creates events as needed.
RaceEventBus Implementation Part 2
public static void Unsubscribe (RaceEventType eventType, UnityAction listener)
{
if (_eventDictionary.ContainsKey(eventType))
{
_eventDictionary[eventType].RemoveListener(listener);
}
}
public static void Publish (RaceEventType eventType)
{
if (_eventDictionary.ContainsKey(eventType))
{
_eventDictionary[eventType].Invoke();
}
}
}
}
Complete Event Bus API
Subscribe(event, method) - Register to listen
Unsubscribe(event, method) - Stop listening
Publish(event) - Broadcast to all listeners
CountdownTimer Subscriber Implementation
using UnityEngine;
using System.Collections;
namespace Chapter.EventBus
{
public class CountdownTimer : MonoBehaviour
{
private float _currentTime;
private float duration = 3.0f;
void OnEnable ()
{
RaceEventBus.Subscribe(RaceEventType.COUNTDOWN, StartTimer);
}
void OnDisable ()
{
RaceEventBus.Unsubscribe(RaceEventType.COUNTDOWN, StartTimer);
}
void StartTimer ()
{
StartCoroutine(Countdown());
}
private IEnumerator Countdown ()
{
_currentTime = duration;
while (_currentTime > 0)
{
Debug.Log("Countdown: " + Mathf.Ceil(_currentTime));
yield return new WaitForSeconds(1.0f);
_currentTime--;
}
Debug.Log("GO!" );
RaceEventBus.Publish(RaceEventType.START);
}
}
}
BikeController Subscriber Implementation
using UnityEngine;
namespace Chapter.EventBus
{
public class BikeController : MonoBehaviour
{
private bool _isRunning;
void OnEnable ()
{
RaceEventBus.Subscribe(RaceEventType.START, StartBike);
RaceEventBus.Subscribe(RaceEventType.STOP, StopBike);
RaceEventBus.Subscribe(RaceEventType.RESTART, RestartBike);
}
void OnDisable ()
{
RaceEventBus.Unsubscribe(RaceEventType.START, StartBike);
RaceEventBus.Unsubscribe(RaceEventType.STOP, StopBike);
RaceEventBus.Unsubscribe(RaceEventType.RESTART, RestartBike);
}
void StartBike ()
{
_isRunning = true ;
Debug.Log("[BikeController] Bike started!" );
}
void StopBike ()
{
_isRunning = false ;
Debug.Log("[BikeController] Bike stopped" );
}
void RestartBike ()
{
transform.position = Vector3.zero;
Debug.Log("[BikeController] Bike restarted at origin" );
}
}
}
HUDController Subscriber Implementation
using UnityEngine;
namespace Chapter.EventBus
{
public class HUDController : MonoBehaviour
{
void OnEnable ()
{
RaceEventBus.Subscribe(RaceEventType.START, DisplayStartMessage);
RaceEventBus.Subscribe(RaceEventType.STOP, DisplayStopMessage);
RaceEventBus.Subscribe(RaceEventType.FINISH, DisplayFinishMessage);
RaceEventBus.Subscribe(RaceEventType.PAUSE, DisplayPauseMessage);
}
void OnDisable ()
{
RaceEventBus.Unsubscribe(RaceEventType.START, DisplayStartMessage);
RaceEventBus.Unsubscribe(RaceEventType.STOP, DisplayStopMessage);
RaceEventBus.Unsubscribe(RaceEventType.FINISH, DisplayFinishMessage);
RaceEventBus.Unsubscribe(RaceEventType.PAUSE, DisplayPauseMessage);
}
void DisplayStartMessage ()
{
Debug.Log("[HUD] GO! Race has started!" );
}
void DisplayStopMessage ()
{
Debug.Log("[HUD] Race stopped" );
}
void DisplayFinishMessage ()
{
Debug.Log("[HUD] FINISH! You completed the race!" );
}
void DisplayPauseMessage ()
{
Debug.Log("[HUD] PAUSED" );
}
}
}
Testing the Event Bus
Test Setup Steps
Create new Unity scene
Create empty GameObject "RaceManager"
Attach CountdownTimer, BikeController, HUDController scripts
Create test client script (next slide)
Play and use GUI buttons to publish events
Watch console for subscriber responses!
Testing Strategy: Use GUI buttons to manually publish events,
verify all subscribers respond correctly in the console.
ClientEventBus Test Script
using UnityEngine;
namespace Chapter.EventBus
{
public class ClientEventBus : MonoBehaviour
{
private bool _isButtonEnabled;
void Start ()
{
_isButtonEnabled = true ;
}
void OnEnable ()
{
RaceEventBus.Subscribe(RaceEventType.FINISH, EnableButton);
}
void OnDisable ()
{
RaceEventBus.Unsubscribe(RaceEventType.FINISH, EnableButton);
}
void EnableButton ()
{
_isButtonEnabled = true ;
}
void OnGUI ()
{
if (GUILayout.Button("Start Countdown" ))
{
_isButtonEnabled = false ;
RaceEventBus.Publish(RaceEventType.COUNTDOWN);
}
if (_isButtonEnabled)
{
if (GUILayout.Button("Stop Race" ))
RaceEventBus.Publish(RaceEventType.STOP);
if (GUILayout.Button("Restart Race" ))
RaceEventBus.Publish(RaceEventType.RESTART);
if (GUILayout.Button("Finish Race" ))
RaceEventBus.Publish(RaceEventType.FINISH);
if (GUILayout.Button("Pause" ))
RaceEventBus.Publish(RaceEventType.PAUSE);
}
}
}
}
Hands-On Implementation
35-Minute Implementation Challenge
Implement the complete Event Bus system:
Create RaceEventType enum (7 events)
Create RaceEventBus static class (Subscribe, Unsubscribe, Publish)
Create CountdownTimer subscriber
Create BikeController subscriber
Create HUDController subscriber
Create ClientEventBus test script
Test all events with GUI buttons
Start 35-Minute Timer
Remember: OnEnable = Subscribe, OnDisable = Unsubscribe!
Memory Leak Prevention
β οΈ THE MOST IMPORTANT RULE
ALWAYS unsubscribe in OnDisable() or OnDestroy()
The OnEnable/OnDisable Pattern
void OnEnable ()
{
RaceEventBus.Subscribe(RaceEventType.START, MyMethod);
}
void OnDisable ()
{
RaceEventBus.Unsubscribe(RaceEventType.START, MyMethod);
}
β What Happens Without Unsubscribe
Event bus holds reference to destroyed objects
Memory is never released (memory leak)
Null reference exceptions when events fire
Performance degradation over time
Event Bus vs Observer Pattern
Event Bus
Centralized: Single static messaging hub
Decoupled: No direct object references
Global: Accessible anywhere
Anonymous: Publishers don't know subscribers
Use for: Game-wide events
Observer Pattern
Distributed: Each subject has own observers
Direct: Subjects hold observer references
Local: Subject-specific subscriptions
Known: Subjects manage their observers
Use for: Component-specific notifications
Next Week: We'll learn the Observer Pattern and see the differences in practice!
Event Bus vs Unity Events
Event Bus (This Lecture)
Code-based: Subscribe via scripts
Static: Globally accessible
Runtime: Dynamic subscription
Type-safe: Enum-based events
Best for: System-level events
Unity Events (Inspector)
Visual: Wire up in Inspector
Component: Per-object instances
Design-time: Set up before runtime
Serialized: Saved with scene
Best for: UI buttons, triggers
Our Implementation: RaceEventBus uses UnityEvent internally but
manages subscriptions through code for flexibility!
When NOT to Use Event Bus
β High-Frequency Events
Don't use for Update()-like events or per-frame updates. The dictionary lookup
overhead adds up. Use direct method calls instead.
β Events Requiring Return Values
Event Bus is fire-and-forget. If you need a return value or acknowledgment,
use direct method calls or callbacks.
β Guaranteed Execution Order
You can't control the order subscribers are notified. If order matters,
use a different pattern.
β Simple Parent-Child Communication
For direct component relationships, just use GetComponent() or public references.
Don't over-engineer!
Alternative & Related Patterns
ScriptableObject Events
Unity-specific pattern using ScriptableObjects as event channels.
Data persists between scenes, designer-friendly.
Use for: Cross-scene events, data-driven events
Message Queue
Events stored in queue and processed in order. Better performance control,
can batch process or defer.
Use for: Network events, async operations
C# Events
Built-in C# event system with delegates. More type-safe, no Unity dependency,
standard .NET pattern.
Use for: Pure C# systems, non-Unity code
Reactive Extensions (Rx)
Advanced event streaming with LINQ-like operators. Powerful but complex,
requires UniRx package.
Use for: Complex event chains, async streams
Homework Assignment
Assignment: Event Bus Implementation
Due: Next class
Submission: Unity project + video to D2L
Part 1: Core Event Bus (40 points)
Implement RaceEventType enum with all 7 events
Implement static RaceEventBus class:
Dictionary<RaceEventType, UnityEvent>
Subscribe(eventType, listener) method
Unsubscribe(eventType, listener) method
Publish(eventType) method
Create test scene with ClientEventBus GUI buttons
Homework Assignment (Continued)
Part 2: Subscribers (40 points)
Implement 4 subscriber classes:
CountdownTimer: Listens for COUNTDOWN, uses coroutine, publishes START
BikeController: Listens for START, STOP, RESTART
HUDController: Listens for START, STOP, FINISH, PAUSE
AudioManager: (Create new!) Listens for START, FINISH, PAUSE
Logs appropriate sound messages
All subscribers MUST use OnEnable/OnDisable pattern
Part 3: Keyboard Controls (20 points)
Add keyboard shortcuts to ClientEventBus:
C = Countdown, S = Stop, R = Restart
F = Finish, P = Pause, Q = Quit
Grading Rubric (100 points)
Point Breakdown
15 pts: RaceEventType enum correctly defined
25 pts: RaceEventBus static class with Subscribe/Unsubscribe/Publish
30 pts: 4 subscriber classes (CountdownTimer, BikeController, HUDController, AudioManager)
10 pts: OnEnable/OnDisable pattern used correctly in all subscribers
10 pts: Keyboard controls implemented and working
10 pts: Video demonstrates all events firing with console output
Additional Resources
Video Recording Requirements
Length: 3-5 minutes
Show: Code files + Unity Editor + Console output
Demonstrate: All 7 events working via keyboard
Narration: Brief explanation of your implementation
Questions & Discussion
Open Floor
Event Bus vs Observer - what's the difference?
When should I use Event Bus vs direct references?
How do I debug event flow?
What about event parameters/data?
Memory leak prevention questions?
Homework clarifications?
Start 10-Minute Q&A
Office Hours: Need help with coroutines or UnityEvent? Come see me!
D2L Submission Instructions
How to Submit
Log in to D2L course shell
Navigate to Assignments β Event Bus Pattern Implementation
Upload your video file
In the comments, include:
Your name
Any challenges you encountered
What you learned about decoupling
Submit before deadline
File Size: If your video is too large for D2L, upload to YouTube
(unlisted) or Google Drive and submit the link instead.
Pro Tip: Record your console output alongside your Unity editor to show
all subscribers reacting to events!
Event Bus Pattern Mastered!
Today's Achievements:
β
Understood Event Bus publish-subscribe pattern
β
Identified tight coupling problems
β
Implemented centralized event management
β
Learned UnityEvent vs C# events
β
Built race countdown system
β
Mastered OnEnable/OnDisable memory leak prevention
Homework Due Next Class:
Event Bus + 4 Subscribers + Keyboard Controls
Video submission to D2L
Next: Command Pattern for replay systems!
Previous
1 / 28
Next