Event Aggregator
The Event Aggregator lets parts of the project react to gameplay changes without direct references. When a component publishes a typed message (e.g., BatteryMessage) on a Channel, the Event Aggregator delivers that message to every handler that subscribed for that same message type on that same Channel.
For example, the BatterySystem publishes BatteryMessage containing its battery level on the HUD channel. A UI object with a BatteryMessage handler on the same HUD channel uses the battery level to update its slider when the message arrives.
This keeps things decoupled: the battery code doesn’t know about the UI, and the UI can change without directly touching the battery system.
-
Message — a data payload delivered to listeners.
-
Publisher — a component that sends a message (e.g.,
BatterySystemsendingBatteryMessage). -
Relay — a ready-made subscriber that exposes a UnityEvent so designers can wire things up in the Inspector.
-
Channel Key — a ScriptableObject used as the routing token for a conversation. Messages only flow when the publisher and the handler are in the same channel.
-
Handler — a listener (implements
IHandle<TMessage>) for a specific message type. It subscribes on a channel and runs when that message arrives on that channel. A component can have multiple handlers. -
Event Aggregator — the service that receives published messages and dispatches them to all matching handlers based on the message type and channel key.
Defining the Message
When you use the Event Aggregator, the first step is to define the payload you want to broadcast. The payload should contain exactly the information receivers need. All messages must implement the IMessage interface.
// SampleMessage.cs
using mobisim.Core.EventFramework;
public sealed class SampleMessage : IMessage
{
public float value { private set; get; }
public SampleMessage(float value) => this.value = value;
}
Creating a Channel Key
A Channel Key is a small ScriptableObject asset that identifies a routing channel. Delivery only happens when the publisher and the listener reference the exact same key. Leaving the field empty uses the default channel (which only matches other empty fields).
Creating a Channel Key is simple:
- In the Project window, right-click to bring up the context menu.
- In the context menu, select Create > ScriptableObject > Channel Key.
- Name it appropriately and mark it as Addressable.
- Optionally, fill in the Description field to document what the channel is used for.
Routing matches by asset identity. In multi-scene/additive or bundled setups, Unity can load duplicate ScriptableObject instances of the same asset, breaking delivery. Marking the Channel Key Addressable ensures both publisher and listener resolve the same canonical instance at runtime.
Setting Up a Listener
A listener is a component that reacts when a message arrives. Because publishers never reference receivers directly, a listener subscribes to the Event Aggregator on a chosen channel and declares the specific message type it handles.
Subscribing to an Event
Any component can subscribe to any event by implementing the IHandle<TMessage> interface and passing itself to the Event Aggregator through the Subscribe method.
// SampleHandler.cs
using UnityEngine;
using mobisim.Core;
using mobisim.Core.EventFramework;
using mobisim.Core.Service;
public sealed class SampleHandler : MonoBehaviour, IHandle<SampleMessage>
{
// Assign in Inspector (Null = default channel)
[SerializeField] private ChannelKey channel;
// Subscribes all IHandle<T> on this component
void OnEnable()
{
// Since Event Aggregator is a service, we can access it via the Service Locator
if (UnityServiceLocator.Current.TryGet<IEventAggregator>(out var bus))
bus.Subscribe(this, channel);
}
// Unsubscribes to prevent memory leaks
void OnDisable()
{
if (UnityServiceLocator.Current.TryGet<IEventAggregator>(out var bus))
bus.Unsubscribe(this, channel);
}
public void Handle(SampleMessage message)
{
Debug.Log("Received: " + message.value);
}
}
Notice that implementing the IHandle<SampleMessage> interface requires you to implement the method Handle(TMessage message), where TMessage is the message type you are interested in. This method is called when a matching message is published.
To make things easier, the simulator also provides MessageRelay<TMessage>, which streamlines listening for one message type on one channel.
// SampleMessageRelay.cs
using UnityEngine;
using UnityEngine.Events;
using mobisim.Core.EventFramework;
public sealed class SampleMessageRelay : MessageRelay<SampleMessage>
{
// wire to other components in the Inspector
[SerializeField] private UnityEvent<float> onValue;
public override void Handle(SampleMessage message)
{
onValue?.Invoke(message.value);
}
}
The MessageRelay<TMessage> base class handles subscribing (in OnEnable) and unsubscribing (in OnDisable), and exposes a Channel Key field in the Inspector. When you derive from the base class, you must also implement the method Handle(TMessage message). If you override OnEnable or OnDisable in a relay, call the base implementation so the subscription still happens. The simulator ships ready-made relays such as BatteryMessageRelay.
Subscribing Multiple Events Across Different Channels
A single component can listen to more than one message type. Simply add more IHandle<TMessage> interfaces to the subscriber. Notice that the Handle(TMessage message) method is now overloaded with the new message type, and the channels are mixed and matched when subscribing.
// SampleHandlers.cs
using UnityEngine;
using mobisim.Core;
using mobisim.Core.EventFramework;
using mobisim.Core.Service;
public sealed class SampleHandlers : MonoBehaviour, IHandle<SampleMessage>, IHandle<OtherMessage>
{
// Assign in Inspector (Null = default channel)
[SerializeField] private ChannelKey channelA;
[SerializeField] private ChannelKey channelB;
void OnEnable()
{
if (UnityServiceLocator.Current.TryGet<IEventAggregator>(out var bus))
{
bus.Subscribe(this, null); // subscribe to SampleMessage and OtherMessage in the default channel
if(channelA != null) bus.Subscribe<SampleMessage>(this, channelA); // subscribe to SampleMessage in Channel A
if(channelB != null) bus.Subscribe<OtherMessage>(this, channelB); // subscribe to OtherMessage in Channel B
}
}
void OnDisable()
{
if (UnityServiceLocator.Current.TryGet<IEventAggregator>(out var bus))
{
bus.Unsubscribe(this); // unsubscribe from all messages across all channels
}
}
public void Handle(SampleMessage m) => Debug.Log($"Sample: {m.value}");
public void Handle(OtherMessage m) => Debug.Log($"Other: {m.value}");
}
Publishing Messages
Once it has a reference to the IEventAggregator, any component can publish messages. The Publish method accepts any object that implements IMessage and delivers it to all subscribers listening for that exact message type on the same channel.
// SamplePublisher.cs
using UnityEngine;
using mobisim.Core;
using mobisim.Core.EventFramework;
using mobisim.Core.Service;
public sealed class SamplePublisher : MonoBehaviour
{
[SerializeField] private ChannelKey channel; // assign the SAME asset as the listener (or NULL for default)
void Start()
{
if (UnityServiceLocator.Current.TryGet<IEventAggregator>(out var bus))
{
bus.Publish(new SampleMessage(101), channel);
}
}
}