Writing asynchronous, scene-independent event systems in Unity

Jonas Hundertmark
medialesson
Published in
4 min readNov 11, 2021
Photo by John Barkiple on Unsplash

ScriptableObjects are great. They provide an easy-to implement access point for loads of behaviour scripts in your scenes without needing to fall back on a Singleton. This weak form of data binding makes ScriptableObjects ideal candidates for event systems, since you can freely bind and unbind events to MonoBehaviours, across scenes even.

Our implementation of ScriptableObjects as event systems will be largely based off of Ryan Hipple’s talk at Unite Austin 2017. Hipple uses a dedicated Listener MonoBehaviour, which handles all the pesky logic stuff for you, allowing you to hand over the entire event system to designers without them ever needing to open Visual Studio. I like working in code, however, so my list of Listeners will store UnityActions directly:

All your MonoBehaviour needs to do now Listen to a linked event with a coresponsing method, like this:

[SerializeField]
private EventRelay _myEvent = null;
private void Start()
{
_myEvent.Add(OnMyEvent);
}
private void OnMyEvent()
//...

The great thing about this implementation is that a single MonoBehaviour can listen to multiple events without needing a listener script for each individual event itself. This moves some complexity into the MonoBehaviour, which may be undesirable depending on the project and size of your development team. Like I said, I like working in code, so this approach suits me more than having to click and drag a bunch of serialized Event boxes in the inspector window.

Everything is great then, right? We have a powerful and flexible event system, that works independently of scenes, doesn’t require Singletons, and works natively without any plugins. What a nice, uncomplicated way to do events in Unity!

Unfortunately, we’re not quite done yet.

So you have your event system all set up, and maybe some services running in the background. In my case, I have a FileSystemWatcher set up to monitor a particular folder in my persistentData, notifying me whenever something updates in that folder. I edit a file, the EventRelay does its work and…

…nothing. That’s strange. Not even an exception.

Upon further inspection, it turns out we are trying to call our method from outside the main thread (because that’s where our FileSystemWatcher set itself up). Unity doesn’t take kindly to that. We’re also not seeing exceptions because the Unity console log only shows logs from the main thread.

To actually do stuff here, we first need to get back into the main Unity thread somehow. Luckily, there is a relatively easy way to do that: We’ll need PimDeWitte’s UnityMainThreadDispatcher script (which, luckily, still works like a charm 6 years after its initial release) and a little bit of elbow grease. It’s actually way easier than it looks, believe me.

Start by dragging the UnityMainThreadDispatcher script into a root-GameObject of your choice. This is best done at initialization, since the Script will call DontDestroyOnLoad on Start.

Something like this

By default, PimDeWitte’s UnityMainThreadDispatcher will only accept Unity coroutines (i.e. IEnumerator methods) as valid calls. We can, however, just use an empty lambda here, to call what we ultimately want. Finally, our EventRelay script only needs two extra lines of code to work properly:

public virtual void Raise()
{
for (int i = listeners.Count - 1; i >= 0; i--)
{
if (listeners[i] == null)
{
listeners.RemoveAt(i);
continue;
}

var curentAction = listeners[i];
UnityMainThreadDispatcher.Instance()
.Enqueue(() => curentAction.Invoke());
}
}

Please note that you need to store your UnityAction inside a variable before adding it to the Queue, since our UnityMainThreadDispatcher doesn’t know i in this context.

Here is the full script for copy & pasting directly into your project. I’ve also taken the liberty and added an EventRelay with one generic type parameter for overriding. Feel free to expand on it!

And that, finally, is it. Simple, powerful and flexible Events in Unity, powered by the magic of ScriptableObjects. I’ve had a lot of fun ironing out the kinks in my implementation, and in the process, learned a lot about ScriptableObjects themselves. They’re a criminally underutilized part of the engine and I highly recommend you to play around with them a bit yourselves.

--

--