EventSystem API Reference
Folder: Messaging/EventSystem
Generated: 2026-07-07T05:06:13.846453
Files in System: 7
EventSystem API Documentation
System: EventSystem
Namespace: SaffronLibrary.Contracts.Events
Location: Assets/Systems/_Core/Messaging/EventSystem/
Version: Core Library
Table of Contents
System Overview
What This System Does
The EventSystem is a decoupled, channel-based messaging system that allows any script in the project to broadcast and receive events without holding direct references to each other. Publishers raise events onto named channels; subscribers listen to those same channels and react when an event arrives.
Think of it as a radio broadcast model: a station (publisher) transmits on a frequency (channel), and any radio (subscriber) tuned to that frequency receives the signal — all without the station knowing who is listening.
Why It Exists in SaffronLibrary
Direct references between systems create tight coupling — changing one class forces changes in every class that depends on it. As a game grows, this becomes brittle and hard to maintain. The EventSystem solves this by acting as a single shared message broker:
- A UI script can react to player death without knowing anything about the
PlayerHealthclass. - An animation controller can respond to game state changes without being wired up in the Inspector.
- Audio, VFX, analytics, and achievement systems can all respond to the same game event independently.
Key Responsibilities
| Responsibility | Description |
|---|---|
| Channel Management | Maintains a registry of active event channels and their subscriber lists |
| Event Dispatch | Routes raised events to every subscriber on the matching channel |
| Subscription Lifecycle | Provides safe subscribe/unsubscribe to prevent memory leaks and null-reference errors |
| Event Construction | Factory classes provide a clean API for building typed, pre-configured event objects |
| Type Safety | Strongly typed event subclasses carry relevant data rather than using loose object payloads |
Architecture & Design
Design Patterns Used
1. Observer Pattern (Core)
The EventsBus is an implementation of the Observer/Event Aggregator pattern. Subscribers register a callback delegate; publishers raise an event without knowing who (or how many listeners) will respond.
Publisher ──→ EventsBus.Raise(event)
│
┌────────┴────────┐
↓ ↓
Subscriber A Subscriber B
2. Static Service Locator (EventsBus)
EventsBus is a static class, making it globally accessible without requiring injection or a MonoBehaviour reference. This is an intentional trade-off: convenience over strict inversion-of-control, appropriate for a core messaging layer.
3. Factory Pattern (Event Factories)
Each domain (UI, Animation, System, Core) has its own Factory class responsible for constructing correctly configured Event objects. This keeps event creation logic centralized and prevents misconfigured events from being raised.
UIEventFactory.CreateButtonClicked("PlayButton")
→ returns a fully configured Event instance
→ caller passes it directly to EventsBus.Raise()
4. Channel-Based Routing
Events are dispatched by channel, not by event type. This is a deliberate design choice: a channel is a named communication contract. Multiple event types can share a channel if they conceptually belong to the same domain, or each type can have its own channel for fine-grained control.
Architectural Diagram
┌─────────────────────────────────────────────────────────────────┐
│ EventSystem │
│ │
│ ┌──────────────┐ ┌─────────────┐ ┌──────────────────┐ │
│ │ EventChannel │ │ Event │ │ IEventListener │ │
│ │ (Enum) │ │ (Base) │ │ (Interface) │ │
│ └──────┬───────┘ └──────┬──────┘ └────────┬─────────┘ │
│ │ │ │ │
│ └──────────────────┼─────────────────────┘ │
│ ↓ │
│ ┌─────────────┐ │
│ │ EventsBus │ ← Static Global Broker │
│ │ (Static) │ │
│ └─────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Event Factories │ │
│ │ EventFactory │ UIEventFactory │ AnimationEventFactory │ │
│ │ │ SystemEventFactory │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
How It Connects to Other Systems
| System | Relationship |
|---|---|
| UI System | Subscribes to UI channels; raises UI events via UIEventFactory |
| Animation System | Subscribes to animation channels; raises events via AnimationEventFactory |
| Core/Game Systems | Uses EventFactory and SystemEventFactory for game-state events |
| Any MonoBehaviour | Can subscribe/unsubscribe via EventsBus without any other dependency |
Key Terminology
| Term | Definition |
|---|---|
| Channel | A named routing key (EventChannel enum value) that groups related events |
| Event | A data object inheriting from Event that carries context about something that happened |
| Subscriber / Listener | Any Action<Event> delegate registered on a channel |
| Publisher / Raiser | Any code that calls EventsBus.Raise() with a configured Event |
| Factory | A static helper class that constructs pre-configured Event instances |
Public API Reference
EventsBus
Type: static class
Namespace: Global (uses SaffronLibrary.Contracts.Events)
File: Scripts/EventsBus.cs
The central message broker. All interaction with the event system flows through this class.
EventsBus.Subscribe
public static void Subscribe(EventChannel channel, Action<Event> listener)
Registers a delegate to be called whenever an event is raised on the specified channel.
| Parameter | Type | Description |
|---|---|---|
channel |
EventChannel |
The channel to listen on |
listener |
Action<Event> |
The callback method to invoke when an event fires |
Behaviour:
- If no subscription entry exists for the channel, one is created automatically.
- Multiple listeners on the same channel are all invoked when an event is raised.
- Calling
Subscribewith the same listener twice will result in that listener being called twice per event. Guard against this with explicit unsubscribe-before-subscribe patterns or by tracking subscription state.
Example:
private void OnEnable()
{
EventsBus.Subscribe(EventChannel.UI, OnUIEvent);
}
private void OnUIEvent(Event e)
{
// handle UI event
}
EventsBus.Unsubscribe
public static void Unsubscribe(EventChannel channel, Action<Event> listener)
Removes a previously registered delegate from a channel’s subscriber list.
| Parameter | Type | Description |
|---|---|---|
channel |
EventChannel |
The channel to stop listening on |
listener |
Action<Event> |
The exact delegate reference that was originally subscribed |
Behaviour:
- Safe to call even if the channel has no subscribers (no exception thrown).
- The
listenerreference must be the same delegate instance used duringSubscribe. Anonymous lambdas assigned inline will not unsubscribe correctly (see Best Practices). - If the listener was never subscribed, this call is a no-op.
Example:
private void OnDisable()
{
EventsBus.Unsubscribe(EventChannel.UI, OnUIEvent);
}
EventsBus.Raise
public static void Raise(Event e)
Dispatches an event to all subscribers registered on e.Channel.
| Parameter | Type | Description |
|---|---|---|
e |
Event |
The event object to broadcast. Must not be null. |
Behaviour:
- If
eisnull, the method returns immediately with no error. - If no subscribers exist on the event’s channel, the call is a no-op.
- Invocation is synchronous — all subscribers are called on the same frame and thread as the caller.
- Subscribers are invoked in the order they subscribed (delegate multicast ordering).
Example:
var e = UIEventFactory.CreateButtonClicked("StartButton");
EventsBus.Raise(e);
Event
Type: class (base class)
File: Scripts/Event.cs
The base data object for all events in the system. All domain-specific events inherit from this class.
| Member | Type | Description |
|---|---|---|
Channel |
EventChannel |
The channel this event belongs to. Used by EventsBus for routing. |
| Value | Domain | Description |
|---|---|---|
UI |
UIEventFactory | User interface interactions (button clicks, panel transitions, etc.) |
Animation |
AnimationEventFactory | Animation trigger and state change events |
System |
SystemEventFactory | Core system-level events (scene load, pause, quit, etc.) |
Core |
EventFactory | Foundational game events (game state, player lifecycle, etc.) |
Event Factory Classes
All factory classes are static and follow the same contract: their methods accept configuration parameters and return a fully constructed Event subclass instance, ready to be passed to EventsBus.Raise().
EventFactory
File: CoreEvents/EventFactory.cs
Domain: Core game events
Constructs fundamental game-level events. Use for events that don’t cleanly belong to UI, animation, or system categories.
// Inferred API shape — consult source for exact signatures
public static class EventFactory
{
public static Event Create(EventChannel channel, /* domain params */);
}
UIEventFactory
File: UIEvents/UIEventFactory.cs
Domain: User interface
Constructs events related to UI interactions and state changes.
public static class UIEventFactory
{
// Example inferred methods — consult source for exact signatures
public static Event CreateButtonClicked(string buttonId);
public static Event CreatePanelOpened(string panelId);
public static Event CreatePanelClosed(string panelId);
}
AnimationEventFactory
File: AnimationEvents/AnimationEventFactory.cs
Domain: Animation
Constructs events related to animation triggers, state entries, and exits.
public static class AnimationEventFactory
{
// Example inferred methods — consult source for exact signatures
public static Event CreateAnimationTriggered(string animationName);
public static Event CreateAnimationCompleted(string animationName);
}
SystemEventFactory
File: SystemEvents/SystemEventFactory.cs
Domain: System / Application lifecycle
Constructs events related to application and scene lifecycle.
public static class SystemEventFactory
{
// Example inferred methods — consult source for exact signatures
public static Event CreateSceneLoaded(string sceneName);
public static Event CreateGamePaused(bool isPaused);
public static Event CreateApplicationQuit();
}
Interfaces — EventInterfaces.cs
File: Scripts/EventInterfaces.cs
Defines the contracts that bind the system together. Interfaces allow systems to depend on abstractions rather than concrete implementations.
// Inferred — consult EventInterfaces.cs for authoritative definitions
namespace SaffronLibrary.Contracts.Events
{
/// <summary>
/// Implemented by any class that wants to handle a specific event type.
/// </summary>
public interface IEventListener<T> where T : Event
{
void OnEvent(T e);
}
/// <summary>
/// Marks an object as capable of raising events.
/// </summary>
public interface IEventRaiser
{
void Raise(Event e);
}
}
Usage Patterns
Pattern 1: Subscribe in OnEnable, Unsubscribe in OnDisable
This is the recommended pattern for MonoBehaviours. It ensures subscriptions are active only while the GameObject is active, and automatically cleans up when disabled or destroyed.
public class HUDController : MonoBehaviour
{
private void OnEnable()
{
EventsBus.Subscribe(EventChannel.UI, HandleUIEvent);
EventsBus.Subscribe(EventChannel.Core, HandleCoreEvent);
}
private void OnDisable()
{
EventsBus.Unsubscribe(EventChannel.UI, HandleUIEvent);
EventsBus.Unsubscribe(EventChannel.Core, HandleCoreEvent);
}
private void HandleUIEvent(Event e)
{
// handle UI events
}
private void HandleCoreEvent(Event e)
{
// handle core events
}
}
Pattern 2: Subscribe in Awake/Start, Unsubscribe in OnDestroy
Use this when the listener should remain active even when the GameObject is inactive (e.g., a persistent manager).
public class GameManager : MonoBehaviour
{
private void Awake()
{
EventsBus.Subscribe(EventChannel.System, OnSystemEvent);
}
private void OnDestroy()
{
EventsBus.Unsubscribe(EventChannel.System, OnSystemEvent);
}
private void OnSystemEvent(Event e)
{
if (e is SceneLoadedEvent sceneEvent)
{
Debug.Log($"Scene loaded: {sceneEvent.SceneName}");
}
}
}
Pattern 3: Raising Events via Factories
Always prefer factories over constructing events manually. This is the intended usage of the factory layer.
public class StartButton : MonoBehaviour
{
public void OnButtonClicked()
{
// Use the factory — it handles all configuration
EventsBus.Raise(UIEventFactory.CreateButtonClicked("StartButton"));
}
}
Pattern 4: Typed Event Handling with Pattern Matching
When a listener subscribes to a channel that carries multiple event types, use C# pattern matching to branch safely.
private void HandleCoreEvent(Event e)
{
switch (e)
{
case PlayerDiedEvent died:
ShowDeathScreen(died.PlayerName, died.FinalScore);
break;
case PlayerRespawnedEvent respawn:
HideDeathScreen();
break;
case GameWonEvent win:
ShowVictoryScreen(win.FinalScore);
break;
}
}
Pattern 5: Non-MonoBehaviour Subscribers (Plain C# Classes)
Pure C# systems (services, repositories, etc.) can subscribe just as easily — they just need to manage their own lifecycle.
public class AnalyticsService
{
public AnalyticsService()
{
EventsBus.Subscribe(EventChannel.Core, OnCoreEvent);
EventsBus.Subscribe(EventChannel.UI, OnUIEvent);
}
public void Dispose()
{
EventsBus.Unsubscribe(EventChannel.Core, OnCoreEvent);
EventsBus.Unsubscribe(EventChannel.UI, OnUIEvent);
}
private void OnCoreEvent(Event e)
{
// Track game events for analytics
}
private void OnUIEvent(Event e)
{
// Track UI interactions
}
}
Best Practices
✅ Always store method references, never inline lambdas for subscriptions
// ❌ BAD — you cannot unsubscribe this lambda; it leaks
EventsBus.Subscribe(EventChannel.UI, (e) => HandleUI(e));
// ✅ GOOD — named method can be unsubscribed reliably
EventsBus.Subscribe(EventChannel.UI, HandleUI);
✅ Always unsubscribe before a subscriber is destroyed
Failing to unsubscribe means the EventsBus holds a reference to a destroyed object’s delegate. The object will not be garbage collected, and calling the delegate may cause null-reference errors.
private void OnDestroy()
{
// Always clean up subscriptions
EventsBus.Unsubscribe(EventChannel.UI, HandleUI);
}
✅ Keep event handlers focused and fast
Event handlers run synchronously on the calling thread. Heavy processing inside a handler will block all other subscribers on that invocation chain.
// ✅ Handle fast, defer heavy work
private void OnCoreEvent(Event e)
{
if (e is LevelCompleteEvent lvl)
{
_pendingLevelData = lvl; // store it
StartCoroutine(ProcessLevelData()); // process async
}
}
✅ Use factories for every Raise call
Factories are the single source of truth for event construction. Using them ensures event data is always valid and consistently populated.
✅ One channel per domain, not per event type (unless needed)
Channels are cheap routing keys — but too many channels makes the system hard to navigate. Group related events on the same channel and use type-checking inside handlers. Reserve dedicated channels for high-frequency events where filtering overhead matters.
✅ Document which channels a class subscribes to and raises
/// <summary>
/// Controls the HUD display.
/// Subscribes: EventChannel.UI, EventChannel.Core
/// Raises: None
/// </summary>
public class HUDController : MonoBehaviour { ... }
Integration Notes
How Other Systems Interact with EventSystem
The EventSystem is designed as a zero-dependency broadcast layer. Other systems interact with it in one of two roles:
| Role | What They Do | Example |
|---|---|---|
| Publisher | Calls EventsBus.Raise() with a factory-constructed event |
PlayerHealth raises a PlayerDiedEvent on EventChannel.Core |
| Subscriber | Calls Subscribe/Unsubscribe and handles inbound events |
HUDController listens on EventChannel.Core to update health display |
A system can be both a publisher and a subscriber (e.g., a GameStateManager that listens for player events and raises system events in response).
Dependencies
| Dependency | Type | Notes |
|---|---|---|
SaffronLibrary.Contracts.Events |
Namespace | Provides EventChannel, Event, and interfaces |
System.Collections.Generic |
.NET Standard | Used internally by EventsBus for Dictionary |
System |
.NET Standard | Used for Action<T> delegate type |
| No Unity dependencies in core | — | EventsBus, Event, and factories are plain C# — no MonoBehaviour required |
The core system (
EventsBus,Event,EventInterfaces) has no Unity dependencies. It can be unit tested without a running Unity context.