Event Bus Pattern - Managing Game Events

Managing Game Events

The Event Bus Pattern

CSCI 3213 - Game Programming

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.

Developer's Note: "Never Panic Early"

โš ๏ธ What to Expect During Implementation

As we implement this pattern, we'll be creating multiple files in a specific order. You will see errors in Unity and your IDE until all files are complete.

How to Handle Development Errors

  • Don't Ignore: Note the errors - they're telling you something
  • Don't Panic: These are expected until all files are created
  • Stay Calm: Follow the implementation order and errors will resolve

As Apollo 13's Fred Haise said: "Never Panic Early"

This is a critical skill for software development. Note problems, but stay focused on the implementation path.

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(); // Direct dependency! audio.PlayRaceMusic(); // Direct dependency! timer.StartTimer(); // Direct dependency! spawner.BeginSpawning(); // Direct dependency! camera.FollowPlayer(); // Direct dependency! } }

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)

QUIT

Exit to main menu

RaceEventBus Implementation Part 1

๐Ÿ“ File Structure Note

Create a new file: Assets/Scripts/Patterns/EventBus/RaceEventBus.cs
Both the RaceEventType enum and RaceEventBus class go in this ONE file.

Why? Simplicity for learning! In production, you'd typically separate them, but keeping them together makes it easier to understand how they work together.

using UnityEngine; using UnityEngine.Events; using System.Collections.Generic; // Enum defining all possible race events public enum RaceEventType { COUNTDOWN, // Pre-race countdown START, // Race begins STOP, // Race stopped FINISH, // Race completed RESTART, // Reset race PAUSE, // Game paused QUIT // Exit to menu } // Static event bus - globally accessible singleton public static class RaceEventBus { // Dictionary mapping event types to UnityEvents private static readonly Dictionary<RaceEventType, UnityEvent> _eventDictionary = new Dictionary<RaceEventType, UnityEvent>(); // Subscribe to an event public static void Subscribe(RaceEventType eventType, UnityAction listener) { // If event doesn't exist in dictionary, create it if (!_eventDictionary.ContainsKey(eventType)) { _eventDictionary[eventType] = new UnityEvent(); } // Add listener to the event _eventDictionary[eventType].AddListener(listener); }
Key Points: Static class (no instantiation), Dictionary stores UnityEvents, Subscribe auto-creates events as needed.

RaceEventBus Implementation Part 2

// Unsubscribe from an event public static void Unsubscribe(RaceEventType eventType, UnityAction listener) { // If event exists, remove the listener if (_eventDictionary.ContainsKey(eventType)) { _eventDictionary[eventType].RemoveListener(listener); } } // Publish/broadcast an event to all subscribers public static void Publish(RaceEventType eventType) { // If event exists, invoke all listeners 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

Understanding the Dictionary Pattern

The ContainsKey Pattern

All three methods (Subscribe, Unsubscribe, Publish) use the same defensive pattern:

if (_eventDictionary.ContainsKey(eventType)) { // Safe to access the dictionary value _eventDictionary[eventType].SomeAction(); }

โœ… Why Check First?

  • Safety: Prevents KeyNotFoundException
  • Defensive: Handles edge cases gracefully
  • No crashes: If event doesn't exist, do nothing

โŒ Without ContainsKey

  • Crash risk: Accessing missing key throws exception
  • Unsubscribe fails: Can't remove what doesn't exist
  • Publish fails: Can't invoke non-existent event
Pattern Use Cases:
โ€ข Subscribe: Check if event exists, create if not
โ€ข Unsubscribe: Check if event exists before removing listener
โ€ข Publish: Check if event exists before invoking

Unity Coroutines & IEnumerator

What is a Coroutine?

A coroutine is a special method that can pause execution and resume later, allowing time-based operations without blocking the main game loop.

โŒ Regular Method

  • Executes completely in one frame
  • Can't wait or pause
  • Blocks until finished

โœ… Coroutine

  • Can span multiple frames
  • Can pause and resume
  • Doesn't block execution
// Coroutine signature - returns IEnumerator private IEnumerator Countdown() { // Do some work Debug.Log("3..."); // Pause for 1 second (yield control back to Unity) yield return new WaitForSeconds(1.0f); // Resume here after 1 second Debug.Log("2..."); yield return new WaitForSeconds(1.0f); Debug.Log("GO!"); } // Start the coroutine from another method void Start() { StartCoroutine(Countdown()); // Kicks off the coroutine }
Key Points: Return type is IEnumerator, use yield return to pause, must call StartCoroutine() to execute!

CountdownTimer Subscriber Implementation

๐Ÿ“ File Structure Note - PRODUCTION CODE

Create a new file: Assets/Scripts/UI/CountdownTimer.cs
This subscriber demonstrates how to use coroutines with the Event Bus pattern.
โš ๏ธ This code goes into your Unity project for Blade Racer.

using UnityEngine; using System.Collections; 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); } }

Race Countdown Flow

1. ๐ŸŽฎ Player Presses "Start Race"

โ†’ UI publishes COUNTDOWN event

2. ๐Ÿ“ข CountdownTimer Receives Event

โ†’ OnEnable subscribed us to COUNTDOWN

โ†’ StartTimer() is called automatically

3. โฑ๏ธ Coroutine Counts Down

โ†’ "3... 2... 1..."

โ†’ Display updates each second

4. ๐Ÿ Timer Publishes START

โ†’ "GO!" - Race begins!

โ†’ BikeController receives START event

5. ๐Ÿงน Scene Changes / Game Over

โ†’ OnDisable unsubscribes automatically

โ†’ No memory leaks!

Adding Event Bus to BikeController

๐Ÿ“ File Structure Note

Update existing file: Assets/Scripts/Controllers/BikeController.cs
You already have this file from the State Pattern lecture. Add Event Bus subscriptions to it!
Highlighted comments show the code to add.

using UnityEngine; public class BikeController : MonoBehaviour { // ... existing fields and Start() method stay the same ... // ADD: Subscribe to race events when component is enabled void OnEnable() { RaceEventBus.Subscribe(RaceEventType.START, StartBike); RaceEventBus.Subscribe(RaceEventType.STOP, StopBike); RaceEventBus.Subscribe(RaceEventType.RESTART, RestartBike); } // ADD: Unsubscribe when component is disabled void OnDisable() { RaceEventBus.Unsubscribe(RaceEventType.START, StartBike); RaceEventBus.Unsubscribe(RaceEventType.STOP, StopBike); RaceEventBus.Unsubscribe(RaceEventType.RESTART, RestartBike); } // Your existing StartBike() and StopBike() methods work as-is! // They already have the right signature for event handlers // ADD: New method for restart functionality public void RestartBike() { StopBike(); // Stop the bike first transform.position = Vector3.zero; // Reset position transform.rotation = Quaternion.identity; // Reset rotation } }
Key Point: Your existing StartBike() and StopBike() methods already exist! We're just connecting them to the Event Bus. No need to rewrite working code!

HUDController Subscriber Implementation

๐Ÿ“ File Structure Note - PRODUCTION CODE

Create a new file: Assets/Scripts/UI/HUDController.cs
This subscriber shows how UI elements respond to Event Bus messages.
โš ๏ธ This code goes into your Unity project for Blade Racer.

using UnityEngine; public class HUDController : MonoBehaviour { void OnEnable() { // Subscribe to events that affect UI RaceEventBus.Subscribe(RaceEventType.START, DisplayStartMessage); RaceEventBus.Subscribe(RaceEventType.STOP, DisplayStopMessage); RaceEventBus.Subscribe(RaceEventType.FINISH, DisplayFinishMessage); RaceEventBus.Subscribe(RaceEventType.PAUSE, DisplayPauseMessage); } void OnDisable() { // Clean up subscriptions 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!"); // In real implementation: Update UI text, show animation } void DisplayStopMessage() { Debug.Log("[HUD] Race stopped"); } void DisplayFinishMessage() { Debug.Log("[HUD] FINISH! You completed the race!"); } void DisplayPauseMessage() { Debug.Log("[HUD] PAUSED"); } }

ClientEventBus Test Script

๐Ÿ“ File Structure Note - TESTING CODE

Create a new file: Assets/Scripts/Testing/ClientEventBus.cs
This is a temporary test script with GUI buttons to manually trigger events.
โš ๏ธ For testing only - delete after verifying the Event Bus works!

using UnityEngine; public class ClientEventBus : MonoBehaviour { private bool _isButtonEnabled; void Start() { _isButtonEnabled = true; } void OnEnable() { RaceEventBus.Subscribe(RaceEventType.START, EnableButton); RaceEventBus.Subscribe(RaceEventType.FINISH, EnableButton); } void OnDisable() { RaceEventBus.Unsubscribe(RaceEventType.START, EnableButton); 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); } } }

How This Tests Everything

๐ŸŽฎ Button Click Flow:

User clicks โ†’ Publish event โ†’ All subscribers react

๐Ÿ“‹ What Each Button Tests:

Start Countdown โ†’ CountdownTimer starts "3, 2, 1, GO!"

Stop Race โ†’ BikeController.StopBike() + HUD message

Restart Race โ†’ BikeController.RestartBike()

Finish Race โ†’ HUD shows finish + re-enables buttons

Pause โ†’ HUD shows pause message

โœ… Verifies:

โ€ข RaceEventBus publish/subscribe works

โ€ข Multiple subscribers receive same event

โ€ข Countdown โ†’ START chain works

โ€ข Unsubscribe prevents memory leaks

Optional: Unified Test Panel

โš ๏ธ The Problem: Overlapping GUI Buttons

As you implement more patterns, you'll have multiple test scripts (ClientState, ClientEventBus, GUITestClient, etc.) each creating GUI buttons. They all use default GUILayout positioning, which means buttons overlap in the top-left corner!

๐Ÿ“ File Structure Note - OPTIONAL ENHANCEMENT

Create a new file: Assets/Scripts/Testing/TestPanel.cs
This consolidates all test controls into a single draggable window with collapsible sections.
๐Ÿ’ก This is optional - you can continue using individual test scripts if you prefer!

using UnityEngine; public class TestPanel : MonoBehaviour { // Window configuration private Rect _windowRect = new Rect(10, 10, 220, 400); private bool _isMinimized = false; private Vector2 _scrollPosition; // Section expansion state private bool _stateExpanded = true; private bool _eventBusExpanded = true; // private bool _commandExpanded = true; // Uncomment after Lecture 5 // Cached component references private BikeController _bikeController; // private Invoker _invoker; // Uncomment after Lecture 5 (Command Pattern) void Start() { _bikeController = FindFirstObjectByType<BikeController>(); // _invoker = FindFirstObjectByType<Invoker>(); // Uncomment after Lecture 5 } void OnGUI() { _windowRect = GUILayout.Window(0, _windowRect, DrawWindow, "Test Panel"); } void DrawWindow(int windowID) { if (GUILayout.Button(_isMinimized ? "+" : "-")) _isMinimized = !_isMinimized; if (!_isMinimized) { _scrollPosition = GUILayout.BeginScrollView(_scrollPosition); DrawStatePatternSection(); DrawEventBusSection(); // DrawCommandSection(); // Uncomment after Lecture 5 GUILayout.EndScrollView(); } GUI.DragWindow(); // Makes window draggable } // ... section methods on next slide ... }

Key Features

๐Ÿ–ฑ๏ธ Draggable Window

GUILayout.Window() + GUI.DragWindow()

๐Ÿ“ Collapsible Sections

Toggle headers per pattern

๐Ÿ“œ Scroll View

Handles overflow when many sections expanded

โž– Minimize Button

Collapse entire panel to tiny button

โœ… Lazy Null Checks

Sections only appear if components exist

Alternative: Use separate GUILayout.Window() calls with different window IDs for each pattern's test controls.

TestPanel Section Methods

// Continuing TestPanel class... void DrawStatePatternSection() { if (_bikeController == null) return; // Only show if component exists GUI.backgroundColor = Color.cyan; _stateExpanded = GUILayout.Toggle(_stateExpanded, "โ–ผ State Pattern", "button"); GUI.backgroundColor = Color.white; if (_stateExpanded) { GUILayout.BeginVertical("box"); if (GUILayout.Button("Start Bike")) _bikeController.StartBike(); if (GUILayout.Button("Stop Bike")) _bikeController.StopBike(); if (GUILayout.Button("Turn Left")) _bikeController.Turn(Direction.Left); if (GUILayout.Button("Turn Right")) _bikeController.Turn(Direction.Right); GUILayout.EndVertical(); } } void DrawEventBusSection() { GUI.backgroundColor = Color.yellow; _eventBusExpanded = GUILayout.Toggle(_eventBusExpanded, "โ–ผ Event Bus", "button"); GUI.backgroundColor = Color.white; if (_eventBusExpanded) { GUILayout.BeginVertical("box"); if (GUILayout.Button("Countdown")) RaceEventBus.Publish(RaceEventType.COUNTDOWN); if (GUILayout.Button("Stop")) RaceEventBus.Publish(RaceEventType.STOP); if (GUILayout.Button("Restart")) RaceEventBus.Publish(RaceEventType.RESTART); if (GUILayout.Button("Finish")) RaceEventBus.Publish(RaceEventType.FINISH); if (GUILayout.Button("Pause")) RaceEventBus.Publish(RaceEventType.PAUSE); GUILayout.EndVertical(); } } // Uncomment after Lecture 5 (Command Pattern) when Invoker class exists: /* void DrawCommandSection() { if (_invoker == null) return; GUI.backgroundColor = Color.green; _commandExpanded = GUILayout.Toggle(_commandExpanded, "โ–ผ Command Pattern", "button"); GUI.backgroundColor = Color.white; if (_commandExpanded) { GUILayout.BeginVertical("box"); if (GUILayout.Button("Start Recording")) _invoker.StartRecording(); if (GUILayout.Button("Stop Recording")) _invoker.StopRecording(); if (GUILayout.Button("Play Replay")) _invoker.StartReplay(); GUILayout.EndVertical(); } } */
Pattern: Each section checks if its required component exists before rendering. Color-coded headers make sections easy to identify at a glance!

Testing the Event Bus in Blade Racer

In-Game Test Setup

  1. Open your Blade Racer project
  2. Find your bike GameObject (should already have BikeController)
  3. Create empty GameObject "RaceManager"
  4. Attach CountdownTimer and HUDController to RaceManager
  5. Attach ClientEventBus script to RaceManager
  6. Play the game and use GUI buttons to publish events
  7. Watch console for subscriber responses!
Testing Strategy: Use GUI buttons to manually trigger race events. Watch the console to verify CountdownTimer, BikeController, and HUDController all respond!

Expected Console Output: When you click "Start Countdown", you should see messages from all three subscribers responding to the START event after the countdown!

Hands-On Implementation

35-Minute Implementation Challenge

Implement the complete Event Bus system:

  1. Create RaceEventType enum (7 events)
  2. Create RaceEventBus static class (Subscribe, Unsubscribe, Publish)
  3. Create CountdownTimer subscriber
  4. Create BikeController subscriber
  5. Create HUDController subscriber
  6. Create ClientEventBus test script
  7. Test all events with GUI buttons
Remember: OnEnable = Subscribe, OnDisable = Unsubscribe!

Gaming History Moment ๐Ÿ•น๏ธ

BBS Culture: The First Online Gaming Communities (1980s)

Before the internet as we know it, Bulletin Board Systems (BBS) connected gamers through dial-up modems. A player would call a BBS (literally dialing a phone number), connect at 300-2400 baud, and enter a text-based world. Games like Trade Wars 2002 and Legend of the Red Dragon were turn-based because only one player could connect at a time.

The BBS was event-driven: when you took your turn, the system published events - "Player attacked monster," "Player bought ship," "High score achieved." Other players would see these events when they dialed in later. Message boards let players communicate asynchronously. The SysOp (system operator) managed it all from their home computer!

Connection to Event Bus Pattern

BBS games pioneered event-driven architecture! The system published events (player actions, game state changes) to a central "board" that all players could read. Just like our Event Bus broadcasts RACE_START or COUNTDOWN_START to all subscribers, BBS games broadcast player events to anyone listening. This asynchronous, decoupled communication model became the foundation for modern online gaming!

Learn More: BBS: The Documentary (YouTube) | BBS: The Documentary (Internet Archive)

Memory Leak Prevention

โš ๏ธ THE MOST IMPORTANT RULE

ALWAYS unsubscribe in OnDisable() or OnDestroy()

The OnEnable/OnDisable Pattern

void OnEnable()
{
    // Subscribe when component becomes active
    RaceEventBus.Subscribe(RaceEventType.START, MyMethod);
}

void OnDisable()
{
    // CRITICAL: Unsubscribe when component is disabled
    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

Using Outside Resources & Giving Credit

๐ŸŽต Freesound.org - Your Audio Resource

freesound.org is an excellent free resource for sound effects and audio clips. Perfect for adding countdown beeps, race start sounds, and other audio to your projects!

Creating a Credits File

Always document outside resources you use!

  1. Create a file named CREDITS.txt or CREDITS.md in your project root
  2. List all outside resources (sounds, sprites, code, tutorials)
  3. Include URLs and author names
  4. Note the license type (CC0, CC-BY, etc.)
Professional Practice: Proper attribution is not just ethical - it's legally required for many licenses and expected in the industry. Start this habit now!

Homework Assignment

Assignment: Event Bus Implementation

Due: Next class
Submission: Unity project + video to D2L

Part 1: Core Event Bus (40 points)

  1. Create RaceEventBus.cs file containing:
    • RaceEventType enum with all 7 events (COUNTDOWN, START, STOP, FINISH, RESTART, PAUSE, QUIT)
    • RaceEventBus static class (both in same file!)
  2. Implement RaceEventBus class with:
    • Dictionary<RaceEventType, UnityEvent>
    • Subscribe(eventType, listener) method
    • Unsubscribe(eventType, listener) method
    • Publish(eventType) method
  3. Create test scene with ClientEventBus GUI buttons

Homework Assignment (Continued)

Part 2: Subscribers (40 points)

  1. 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
  2. All subscribers MUST use OnEnable/OnDisable pattern

Part 3: Keyboard Controls via TestPanel (20 points)

  • Add keyboard shortcuts to TestPanel.cs (not ClientEventBus):
    • C = Countdown, S = Stop, R = Restart
    • F = Finish, P = Pause, Q = Quit
  • Add a "Show Keymap" button to TestPanel that displays the shortcuts
  • See the next slide for the TestPanel keyboard implementation!

TestPanel Keyboard Controls

๐Ÿ“ Adding Keyboard Shortcuts to TestPanel.cs

Add these features to your TestPanel.cs class to centralize all keyboard shortcuts and provide a helpful keymap popup for users.

// Add these fields to TestPanel class private bool _showKeymap = false; private Rect _keymapRect = new Rect(250, 10, 200, 250); void Update() { // Event Bus keyboard shortcuts if (Input.GetKeyDown(KeyCode.C)) RaceEventBus.Publish(RaceEventType.COUNTDOWN); if (Input.GetKeyDown(KeyCode.S)) RaceEventBus.Publish(RaceEventType.STOP); if (Input.GetKeyDown(KeyCode.R)) RaceEventBus.Publish(RaceEventType.RESTART); if (Input.GetKeyDown(KeyCode.F)) RaceEventBus.Publish(RaceEventType.FINISH); if (Input.GetKeyDown(KeyCode.P)) RaceEventBus.Publish(RaceEventType.PAUSE); if (Input.GetKeyDown(KeyCode.Q)) RaceEventBus.Publish(RaceEventType.QUIT); // Toggle keymap with K key if (Input.GetKeyDown(KeyCode.K)) _showKeymap = !_showKeymap; } void OnGUI() { _windowRect = GUILayout.Window(0, _windowRect, DrawWindow, "Test Panel"); // Show keymap window if enabled if (_showKeymap) _keymapRect = GUILayout.Window(1, _keymapRect, DrawKeymapWindow, "Keyboard Shortcuts"); }
void DrawKeymapWindow(int windowID) { GUILayout.Label("--- Event Bus ---"); GUILayout.Label("C = Countdown"); GUILayout.Label("S = Stop"); GUILayout.Label("R = Restart"); GUILayout.Label("F = Finish"); GUILayout.Label("P = Pause"); GUILayout.Label("Q = Quit"); GUILayout.Space(10); GUILayout.Label("--- General ---"); GUILayout.Label("K = Toggle this keymap"); GUILayout.Space(10); if (GUILayout.Button("Close")) _showKeymap = false; GUI.DragWindow(); } // Add to DrawWindow() after minimize button: if (GUILayout.Button(_showKeymap ? "Hide Keymap (K)" : "Show Keymap (K)")) _showKeymap = !_showKeymap;
Benefits: All keyboard shortcuts in one place! The keymap popup helps users discover available shortcuts. Press K to toggle the keymap anytime.

Grading Rubric (100 points)

Point Breakdown

25 pts: RaceEventBus static class with Subscribe/Unsubscribe/Publish methods
25 pts: 4 subscriber classes (CountdownTimer, BikeController, HUDController, AudioManager)
15 pts: RaceEventType enum correctly defined with all 7 events
15 pts: OnEnable/OnDisable pattern used correctly in all subscribers
10 pts: Keyboard controls in TestPanel with keymap popup
10 pts: Video demonstrates all events firing with console output

Additional Resources

Further Reading

Video Recording Requirements

  • Length: 3-5 minutes
  • Show: Code files + Unity Editor + Console output
  • Demonstrate: All 7 events via keyboard + show keymap popup (press K)
  • 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?
Office Hours: Need help with coroutines or UnityEvent? Come see me!

D2L Submission Instructions

How to Submit

  1. Log in to D2L course shell
  2. Navigate to Assignments โ†’ Event Bus Pattern Implementation
  3. Upload your video file
  4. In the comments, include:
    • Your name
    • Any challenges you encountered
    • What you learned about decoupling
  5. 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:

Homework Due Next Class:

Event Bus + 4 Subscribers + Keyboard Controls

Video submission to D2L

Next: Command Pattern for replay systems!