← Back to Docs

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

  1. System Overview
  2. Architecture & Design
  3. Public API Reference
  4. Usage Patterns
  5. Integration Notes

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 PlayerHealth class.
  • 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 Subscribe with 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 listener reference must be the same delegate instance used during Subscribe. 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 e is null, 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.