No description
Find a file
James Peret fb67cadcd4
Add package.json v1.0.0, CHANGELOG, and installation docs
- package.json: at.kairoscope.kairoengine.events v1.0.0
- Zero dependencies
- Git URL: https://forgejo.jamesperet.com/kairoengine/events.git
- Ready for UPM git URL installation
2026-06-03 16:48:30 -03:00
Runtime Initial commit for reinitialized git repo 2026-01-19 17:21:11 -03:00
ChangeLog.md Add package.json v1.0.0, CHANGELOG, and installation docs 2026-06-03 16:48:30 -03:00
package.json Add package.json v1.0.0, CHANGELOG, and installation docs 2026-06-03 16:48:30 -03:00
Readme.md Add package.json v1.0.0, CHANGELOG, and installation docs 2026-06-03 16:48:30 -03:00
Readme.md.meta Initial commit for reinitialized git repo 2026-01-19 17:21:11 -03:00
Runtime.meta Initial commit for reinitialized git repo 2026-01-19 17:21:11 -03:00

KairoEngine Events v 1.0.0

Overview

The Events module provides a lightweight, string-based event system for decoupled communication between game systems. It implements both broadcast (fire-and-forget) and request/response patterns, enabling components to communicate without direct references.

Installation

Add to your Unity project's Packages/manifest.json:

"at.kairoscope.kairoengine.events": "https://forgejo.jamesperet.com/kairoengine/events.git"

Or use the Unity Package Manager: Add package from git URLhttps://forgejo.jamesperet.com/kairoengine/events.git

Purpose

The Events module serves as the foundation for:

  • Decoupled Communication: Allow systems to communicate without tight coupling
  • Broadcast Events: Fire-and-forget event notifications with typed payloads
  • Request/Response Pattern: Query data from listeners and receive typed responses
  • Type-Safe Messaging: Support for various payload types while maintaining type safety

Dependencies

  • UnityEngine - Core Unity framework

Architecture

The Events module is accessed through the static EventManager class, which provides two communication subsystems:

public static class EventManager
{
    public static RequestEvents request;   // Request/response pattern
    public static GenericEvents broadcast;  // Fire-and-forget pattern
}

Classes

EventManager

Purpose: Static entry point for the event system.

Usage:

// Broadcast an event
EventManager.broadcast.Trigger("PlayerDied");

// Request data
var response = EventManager.request.GetBool("IsGamePaused");
if (response.status == EventResponseStatus.OK)
{
    bool isPaused = response.value;
}

EventResponse

Purpose: Wrapper for request responses that includes status information.

Structure:

public class EventResponse<T>
{
    public EventResponseStatus status;
    public T value;
}

public enum EventResponseStatus
{
    OK,            // Request successful, value is valid
    NotFound,      // No listener registered for this event
    EmptyResponse  // Listener exists but returned null/empty
}

Usage Pattern:

var response = EventManager.request.GetGameObject("GetPlayerObject");

switch (response.status)
{
    case EventResponseStatus.OK:
        // Use response.value safely
        GameObject player = response.value;
        break;
    case EventResponseStatus.NotFound:
        Debug.LogWarning("No listener for GetPlayerObject");
        break;
    case EventResponseStatus.EmptyResponse:
        Debug.LogWarning("Listener returned null");
        break;
}

GenericEvents

Purpose: Implements broadcast/observer pattern for fire-and-forget event notifications.

Key Features:

  • Multiple Payload Types: Supports various parameter combinations
  • Method Overloading: Automatic type resolution based on listener signature
  • Multi-Cast Delegates: Multiple listeners can subscribe to the same event

Supported Event Types:

Empty Events (No Parameters)

// Listen
EventManager.broadcast.StartListening("GameStarted", OnGameStarted);
void OnGameStarted() { }

// Trigger
EventManager.broadcast.Trigger("GameStarted");

// Stop listening
EventManager.broadcast.StopListening("GameStarted", OnGameStarted);

Single Parameter Events

Supported types: string, float, int, Vector3, Vector3Int, GameObject, MonoBehaviour, ScriptableObject, object

// String event
EventManager.broadcast.StartListening("ShowMessage", OnShowMessage);
void OnShowMessage(string message) { }
EventManager.broadcast.Trigger("ShowMessage", "Hello World");

// Float event
EventManager.broadcast.StartListening("HealthChanged", OnHealthChanged);
void OnHealthChanged(float newHealth) { }
EventManager.broadcast.Trigger("HealthChanged", 75.5f);

// Vector3 event
EventManager.broadcast.StartListening("PlayerMoved", OnPlayerMoved);
void OnPlayerMoved(Vector3 position) { }
EventManager.broadcast.Trigger("PlayerMoved", new Vector3(1, 2, 3));

Multi-Parameter Events

Supported combinations:

  • (float, float) - Two floats
  • (string, int) - String and int
  • (string, float) - String and float
  • (string, string) - Two strings
  • (string, Vector3) - String and Vector3
  • (string, bool) - String and bool
// String + Int event
EventManager.broadcast.StartListening("ItemCollected", OnItemCollected);
void OnItemCollected(string itemName, int quantity) { }
EventManager.broadcast.Trigger("ItemCollected", "Gold", 50);

// String + Vector3 event
EventManager.broadcast.StartListening("SpawnEffect", OnSpawnEffect);
void OnSpawnEffect(string effectName, Vector3 position) { }
EventManager.broadcast.Trigger("SpawnEffect", "Explosion", playerPos);

// Float + Float event
EventManager.broadcast.StartListening("RangeUpdated", OnRangeUpdated);
void OnRangeUpdated(float min, float max) { }
EventManager.broadcast.Trigger("RangeUpdated", 0f, 100f);

Special Object Event

For generic object payloads, use the explicit methods:

EventManager.broadcast.StartListeningForObject("DataReceived", OnDataReceived);
void OnDataReceived(object data) { }
EventManager.broadcast.TriggerForObject("DataReceived", customData);
EventManager.broadcast.StopListeningForObject("DataReceived", OnDataReceived);

RequestEvents

Purpose: Implements request/response pattern for querying data from registered listeners.

Key Features:

  • Type-Safe Responses: Returns EventResponse<T> with status codes
  • Single Listener: Only one listener can be bound per event title
  • Error Handling: Status codes indicate success or failure reasons

Supported Request Types:

Boolean Requests

// Bind a listener
EventManager.request.Bind("IsPlayerAlive", () => playerHealth > 0);

// Request the value
EventResponse<bool> response = EventManager.request.GetBool("IsPlayerAlive");
if (response.status == EventResponseStatus.OK)
{
    if (response.value)
    {
        // Player is alive
    }
}

// Unbind
EventManager.request.Unbind("IsPlayerAlive", listenerMethod);

GameObject Requests

// Bind a listener
EventManager.request.Bind("GetMainCamera", () => Camera.main.gameObject);

// Request the value
EventResponse<GameObject> response = EventManager.request.GetGameObject("GetMainCamera");
if (response.status == EventResponseStatus.OK)
{
    GameObject camera = response.value;
}
else if (response.status == EventResponseStatus.EmptyResponse)
{
    Debug.LogWarning("Camera returned null");
}

Common Patterns

Event-Driven UI Updates

public class HealthUI : MonoBehaviour
{
    void OnEnable()
    {
        EventManager.broadcast.StartListening("PlayerHealthChanged", UpdateHealthBar);
    }

    void OnDisable()
    {
        EventManager.broadcast.StopListening("PlayerHealthChanged", UpdateHealthBar);
    }

    void UpdateHealthBar(float health)
    {
        // Update UI
    }
}

// Trigger from player
public class Player : MonoBehaviour
{
    void TakeDamage(float damage)
    {
        currentHealth -= damage;
        EventManager.broadcast.Trigger("PlayerHealthChanged", currentHealth);
    }
}

Cross-System Queries

// GameManager provides data
public class GameManager : MonoBehaviour
{
    void Start()
    {
        EventManager.request.Bind("IsGamePaused", () => isPaused);
        EventManager.request.Bind("GetPlayer", () => playerObject);
    }
}

// Other systems query data
public class EnemyAI : MonoBehaviour
{
    void Update()
    {
        var pauseResponse = EventManager.request.GetBool("IsGamePaused");
        if (pauseResponse.status == EventResponseStatus.OK && pauseResponse.value)
        {
            return; // Game is paused, skip AI update
        }

        // AI logic...
    }
}

Level Loading Notifications

public class LevelManager : MonoBehaviour
{
    async UniTask LoadLevel(string levelName)
    {
        EventManager.broadcast.Trigger("LevelLoadStarted", levelName);

        // Load level...
        await SceneManager.LoadSceneAsync(levelName);

        EventManager.broadcast.Trigger("LevelLoadComplete", levelName);
    }
}

public class LoadingScreen : MonoBehaviour
{
    void OnEnable()
    {
        EventManager.broadcast.StartListening("LevelLoadStarted", OnLoadStarted);
        EventManager.broadcast.StartListening("LevelLoadComplete", OnLoadComplete);
    }

    void OnLoadStarted(string levelName)
    {
        // Show loading screen
    }

    void OnLoadComplete(string levelName)
    {
        // Hide loading screen
    }
}

Best Practices

  1. Use Consistent Event Names: Define event names as constants to avoid typos

    public static class GameEvents
    {
        public const string PLAYER_DIED = "PlayerDied";
        public const string GAME_PAUSED = "GamePaused";
    }
    
  2. Clean Up Listeners: Always unsubscribe in OnDisable() or OnDestroy() to prevent memory leaks

    void OnDestroy()
    {
        EventManager.broadcast.StopListening("MyEvent", OnMyEvent);
    }
    
  3. Check Response Status: Always check EventResponse.status before using values

    var response = EventManager.request.GetGameObject("GetPlayer");
    if (response.status == EventResponseStatus.OK && response.value != null)
    {
        // Safe to use response.value
    }
    
  4. Prefer Specific Types: Use specific event types over generic object when possible for type safety

  5. Document Event Contracts: Document what parameters events expect and when they're triggered

  6. Consider Performance: String-based lookups have overhead; avoid triggering events in tight loops

  7. Use Broadcasts for Notifications: Use GenericEvents when you don't need a return value

  8. Use Requests for Queries: Use RequestEvents when you need to get data from other systems

Integration with KairoEngine Modules

The Events module is used throughout KairoEngine and World Dweller:

  • InitializationManager: Broadcasts initialization lifecycle events
  • UI Module: Triggers view change events
  • Game Systems: Cross-system communication without dependencies
  • Save/Load Systems: Notifies listeners of save/load operations

Limitations

  • String-Based Lookups: Performance overhead compared to direct references
  • No Type Safety at Compile Time: Event names and parameter types verified at runtime
  • Single Listener for Requests: RequestEvents only supports one listener per event name
  • No Event History: Events are not queued or recorded
  • No Priority System: Listeners are called in registration order

Version History

  • v1.0.0: Initial version
    • Generic broadcast events with multiple payload types
    • Request/response pattern with status codes
    • Support for Unity types (Vector3, GameObject, etc.)