> ## Documentation Index
> Fetch the complete documentation index at: https://docs-unity.molca.id/llms.txt
> Use this file to discover all available pages before exploring further.

# Create and dispatch custom events

> Use EventDispatcher for decoupled cross-component communication.

**Applies to:** Molca Core

## Overview

This recipe shows you how to use the `EventDispatcher` service to implement decoupled communication between components. You'll learn how to inject the EventDispatcher, register event handlers, dispatch events from publisher components, and properly clean up subscriptions. This pattern is ideal for cross-system notifications where components shouldn't have direct references to each other.

## Prerequisites

* **SDK modules**: Molca Core installed
* **Unity setup**: RuntimeManager configured in your scene
* **Prior knowledge**: [Dependency injection](/core/dependency-injection), [EventDispatcher](/core/event-dispatcher)
* **Recommended**: Complete [Recipe: Implement dependency injection](/recipes/implement-dependency-injection) first

## Step-by-step

### Step 1: Inject EventDispatcher into components

Use the `[Inject]` attribute to get the EventDispatcher service in both publisher and subscriber components.

```csharp theme={null}
using UnityEngine;
using Molca;
using Molca.Events;

public class EventPublisher : MonoBehaviour
{
    [Inject] private EventDispatcher _eventDispatcher;

    private async void Start()
    {
        await RuntimeManager.WaitForInitialization();
        
        if (_eventDispatcher == null)
        {
            Debug.LogError("EventDispatcher injection failed");
        }
    }
}
```

**Why this works**: The `[Inject]` attribute tells RuntimeManager to automatically populate the `_eventDispatcher` field after initialization. This eliminates the need for singleton patterns or manual service lookups.

### Step 2: Register event handlers in OnEnable

Subscribe to events in `OnEnable` using `RegisterEvent`. Use parameterless registration for simple notifications or generic registration for typed payloads.

```csharp theme={null}
using UnityEngine;
using Molca;
using Molca.Events;

public class EventSubscriber : MonoBehaviour
{
    [Inject] private EventDispatcher _eventDispatcher;

    private async void Start()
    {
        await RuntimeManager.WaitForInitialization();
    }

    private void OnEnable()
    {
        if (_eventDispatcher == null) return;

        // Register for parameterless event
        _eventDispatcher.RegisterEvent("GameStarted", OnGameStarted);
        
        // Register for typed-payload event
        _eventDispatcher.RegisterEvent<int>("ScoreChanged", OnScoreChanged);
        _eventDispatcher.RegisterEvent<string>("PlayerNameChanged", OnPlayerNameChanged);
    }

    private void OnGameStarted()
    {
        Debug.Log("Game has started");
    }

    private void OnScoreChanged(int newScore)
    {
        Debug.Log($"Score changed to: {newScore}");
    }

    private void OnPlayerNameChanged(string playerName)
    {
        Debug.Log($"Player name: {playerName}");
    }
}
```

**Why this works**: Registering in `OnEnable` ensures handlers are active whenever the component is enabled. The `RegisterEvent<T>` generic method allows type-safe payload delivery without casting.

### Step 3: Unregister handlers in OnDisable

Always unregister event handlers in `OnDisable` to prevent memory leaks and errors when the component is destroyed.

```csharp theme={null}
using UnityEngine;
using Molca;
using Molca.Events;

public class EventSubscriber : MonoBehaviour
{
    [Inject] private EventDispatcher _eventDispatcher;

    private void OnEnable()
    {
        if (_eventDispatcher == null) return;
        
        _eventDispatcher.RegisterEvent("GameStarted", OnGameStarted);
        _eventDispatcher.RegisterEvent<int>("ScoreChanged", OnScoreChanged);
    }

    private void OnDisable()
    {
        if (_eventDispatcher == null) return;
        
        // Unregister all handlers to prevent memory leaks
        _eventDispatcher.UnregisterEvent("GameStarted", OnGameStarted);
        _eventDispatcher.UnregisterEvent<int>("ScoreChanged", OnScoreChanged);
    }

    private void OnGameStarted() { }
    private void OnScoreChanged(int newScore) { }
}
```

**Why this works**: Failing to unregister causes destroyed objects' callbacks to remain registered, leading to `MissingReferenceException` errors. The `OnEnable`/`OnDisable` pairing ensures proper cleanup across enable/disable cycles.

### Step 4: Dispatch events from publisher components

Use `DispatchEvent` to publish events. Other components registered for that event will receive the notification immediately.

```csharp theme={null}
using UnityEngine;
using Molca;
using Molca.Events;

public class GameController : MonoBehaviour
{
    [Inject] private EventDispatcher _eventDispatcher;
    
    private int _currentScore = 0;

    private async void Start()
    {
        await RuntimeManager.WaitForInitialization();
    }

    public void StartGame()
    {
        // Dispatch parameterless event
        _eventDispatcher?.DispatchEvent("GameStarted");
    }

    public void AddScore(int points)
    {
        _currentScore += points;
        
        // Dispatch typed-payload event
        _eventDispatcher?.DispatchEvent<int>("ScoreChanged", _currentScore);
    }

    public void SetPlayerName(string name)
    {
        // Dispatch string payload event
        _eventDispatcher?.DispatchEvent<string>("PlayerNameChanged", name);
    }
}
```

**Why this works**: `DispatchEvent` invokes all registered callbacks for that event name immediately. The generic `DispatchEvent<T>` version passes the payload to all handlers registered with matching type parameter.

### Step 5: Use EventConstants for type-safe event names

Instead of magic strings, use `EventConstants` or define your own constants class for better maintainability and refactoring support.

```csharp theme={null}
using UnityEngine;
using Molca;
using Molca.Events;

// Define your own event constants
public static class GameEvents
{
    public const string GameStarted = "Game.Started";
    public const string GameEnded = "Game.Ended";
    public const string ScoreChanged = "Game.ScoreChanged";
    public const string LevelCompleted = "Game.LevelCompleted";
}

public class TypeSafeEventExample : MonoBehaviour
{
    [Inject] private EventDispatcher _eventDispatcher;

    private async void Start()
    {
        await RuntimeManager.WaitForInitialization();
    }

    private void OnEnable()
    {
        if (_eventDispatcher == null) return;
        
        // Use constants instead of magic strings
        _eventDispatcher.RegisterEvent(GameEvents.GameStarted, OnGameStarted);
        _eventDispatcher.RegisterEvent<int>(GameEvents.ScoreChanged, OnScoreChanged);
    }

    private void OnDisable()
    {
        if (_eventDispatcher == null) return;
        
        _eventDispatcher.UnregisterEvent(GameEvents.GameStarted, OnGameStarted);
        _eventDispatcher.UnregisterEvent<int>(GameEvents.ScoreChanged, OnScoreChanged);
    }

    public void PublishGameStart()
    {
        _eventDispatcher?.DispatchEvent(GameEvents.GameStarted);
    }

    private void OnGameStarted() { }
    private void OnScoreChanged(int score) { }
}
```

**Why this works**: Constants provide compile-time checking, IDE autocomplete, and safe refactoring. Typos in event names become compiler errors instead of silent runtime failures.

## Complete example

Here's a complete example showing a training scenario with multiple components communicating via events:

```csharp theme={null}
using UnityEngine;
using Molca;
using Molca.Events;

// Event constants for the training scenario
public static class TrainingEvents
{
    public const string StepCompleted = "Training.StepCompleted";
    public const string TargetHighlighted = "Training.TargetHighlighted";
    public const string FeedbackRequested = "Training.FeedbackRequested";
}

// Publisher: Training step that dispatches completion events
public class TrainingStep : MonoBehaviour
{
    [SerializeField] private string stepName = "Step 1";
    [Inject] private EventDispatcher _eventDispatcher;

    private async void Start()
    {
        await RuntimeManager.WaitForInitialization();
    }

    public void CompleteStep()
    {
        Debug.Log($"{stepName} completed");
        
        // Notify all subscribers that this step is complete
        _eventDispatcher?.DispatchEvent<string>(TrainingEvents.StepCompleted, stepName);
    }

    public void HighlightTarget(string targetId)
    {
        // Request UI to highlight a specific target
        _eventDispatcher?.DispatchEvent<string>(TrainingEvents.TargetHighlighted, targetId);
    }
}

// Subscriber: UI controller that responds to training events
public class TrainingUIController : MonoBehaviour
{
    [SerializeField] private TMPro.TextMeshProUGUI statusText;
    [Inject] private EventDispatcher _eventDispatcher;

    private async void Start()
    {
        await RuntimeManager.WaitForInitialization();
    }

    private void OnEnable()
    {
        if (_eventDispatcher == null) return;
        
        // Subscribe to training events
        _eventDispatcher.RegisterEvent<string>(TrainingEvents.StepCompleted, OnStepCompleted);
        _eventDispatcher.RegisterEvent<string>(TrainingEvents.TargetHighlighted, OnTargetHighlighted);
    }

    private void OnDisable()
    {
        if (_eventDispatcher == null) return;
        
        // Always clean up subscriptions
        _eventDispatcher.UnregisterEvent<string>(TrainingEvents.StepCompleted, OnStepCompleted);
        _eventDispatcher.UnregisterEvent<string>(TrainingEvents.TargetHighlighted, OnTargetHighlighted);
    }

    private void OnStepCompleted(string stepName)
    {
        statusText.text = $"Completed: {stepName}";
        Debug.Log($"UI updated for completed step: {stepName}");
    }

    private void OnTargetHighlighted(string targetId)
    {
        statusText.text = $"Focus on: {targetId}";
        Debug.Log($"Highlighting target: {targetId}");
    }
}

// Subscriber: Progress tracker that logs all completed steps
public class ProgressTracker : MonoBehaviour
{
    [Inject] private EventDispatcher _eventDispatcher;
    private int _completedSteps = 0;

    private async void Start()
    {
        await RuntimeManager.WaitForInitialization();
    }

    private void OnEnable()
    {
        if (_eventDispatcher == null) return;
        _eventDispatcher.RegisterEvent<string>(TrainingEvents.StepCompleted, OnStepCompleted);
    }

    private void OnDisable()
    {
        if (_eventDispatcher == null) return;
        _eventDispatcher.UnregisterEvent<string>(TrainingEvents.StepCompleted, OnStepCompleted);
    }

    private void OnStepCompleted(string stepName)
    {
        _completedSteps++;
        Debug.Log($"Progress: {_completedSteps} steps completed (latest: {stepName})");
        
        // Could save progress to DataManager here
    }
}
```

This example demonstrates:

* Multiple subscribers listening to the same event
* Type-safe event names using constants
* Proper registration/unregistration lifecycle
* Decoupled communication (components don't reference each other)
* Publisher doesn't know who's listening

## Troubleshooting

* **Event not received**: Confirm `RegisterEvent` runs before the first `DispatchEvent`. If subscription is in `OnEnable` but dispatch fires during `RuntimeManager` initialization, the handler may not be registered yet. Move subscription to `Start` with `await RuntimeManager.WaitForInitialization()`.
* **Duplicate callbacks firing**: Calling `RegisterEvent` twice with the same callback adds it twice. Always pair with `UnregisterEvent` in `OnDisable` to prevent stacking across enable/disable cycles.
* **MissingReferenceException after scene unload**: Destroyed objects' callbacks remain registered. Always call `UnregisterEvent` in `OnDisable` or `OnDestroy`.
* **Wrong type parameter**: The type key must match exactly between publisher and subscriber. `DispatchEvent<int>` will not reach `RegisterEvent<float>` handlers even with the same event name.
* **Events fire but handler doesn't execute**: Check that the subscriber component is enabled and active. Handlers registered in `OnEnable` won't fire if the GameObject is disabled.

## Related

* [EventDispatcher](/core/event-dispatcher) — complete API reference and detailed documentation
* [Dependency injection](/core/dependency-injection) — how to inject EventDispatcher into components
* [Recipe: Implement dependency injection](/recipes/implement-dependency-injection) — prerequisite recipe for using \[Inject]
* [RuntimeManager](/core/runtime-manager) — service container that provides EventDispatcher
