The Event Aggregator Pattern in Unity
Article Purpose
The purpose of this article is to visit a loosely coupled approach in Unity for game/application-wide communication. In using the Event Aggregator design pattern with C#’s Action class, we have a killer combo for creating a simple, efficient, and flexible eventing approach that works across our entire game or application.
Event Aggregator
The Event Aggregator design pattern is simple to implement and to understand, especially if you are familiar with the Observer design pattern. With the Event Aggregator, many different game/application-wide changes (events or actions) can be managed with a single object. I have personally found this pattern to be extremely useful in projects where two distinct and recurring questions present themselves. These questions are:
How do disparate (and often numerous) elements in the game/app:
- Share responsibility for communicating a particular change?
- Update themselves based on a shared interest in a particular change?
The Event Aggregator pattern is a solid approach for solving both problems. It does so by aggregating the access to all/many of the shared interest events. This empowers any object in your game or application to:
- Dispatch specific events
- React to specific events
With great power comes great responsibility however. This approach can be abused, so use it with care. There are situations where the Observer pattern has a best fit.
Unity Example
My personal approach takes three steps:
- Create the Event Aggregator class (I call mine
Signals.cs
) - Define worthy game/application-wide events and their signatures
- Codify these signatures in an API Chunk
An API Chunk is simply a grouping of an event Action
with an accompanying method for triggering the action. Here is an example:
public static event Action<int> OnChangeLevel;
public static void ChangeLevel(int levelIndex){ if(OnChangeLevel != null) OnChangeLevel(levelIndex); }
For the game Crossy Word that I made, you can see two separate runtime actions should, and do, trigger the above ChangeLevel()
method call.
1. Signals.ChangeLevel()
via user interaction
2. Signals.ChangeLevel()
via level completion
In the images above, the UI menu managing class, the game's word tiles managing class, and the game state model managing class are all interested in the Signals.ChangeLevel()
call. Additionally, both the UI menu and the word tiles managing classes, share responsibility for making the call. With the Event Aggregator pattern, shared interest and responsibility is trivially accomplished. The code snippet for reacting to the call is super simple as well:
void OnEnable() {
Signals.OnChangeLevel += OnChangeLevel;
}
All that is left, is the implementation of the OnChangeLevel(int levelIndex)
handler. The various classes that are interested in knowing about the action simply have the following:
void OnChangeLevel(int levelIndex) {
Debug.Log("Change Level to " + levelIndex);
}
It is worth noting that you should clean up after yourself when the action is no longer of interest. You can do so like this:
void OnDisable() {
Signals.OnChangeLevel -= OnChangeLevel;
}
Conclusion
As you can see the Event Aggregator pattern is super simple to implement and extremely powerful and useful for game/application-wide communication. You can build off my example above to better fit your code architecture and/or your team size. Here are a few ideas:
- Leverage namespacing and/or multiple
Signals.cs
classes to group event/action types - Better control access by opting for a
protected
vspublic
accessor - Use inheritance vs the
static
keyword
As always, if you have any thoughts or comments, don't hesitate to contact me on Twitter @derekknox.