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.
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
Step-by-step
Step 1: Inject EventDispatcher into components
Use the [Inject] attribute to get the EventDispatcher service in both publisher and subscriber components.
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.
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.
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.
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.
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:
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.