-
-
Notifications
You must be signed in to change notification settings - Fork 16
Events
When coding, we often reference a script to another. For example, the MechaController
script uses the Unity Rigidbody
script and decides when the method
function should be called. This works fine and is what is done most of the time. However, it creates a dependency between MechaController
and Rigidbody
, one needs the other to work. If we remove the Rigidbody
script, the MechaController
would not work as expected or even crash crash.
Of course, to avoid dependency, we can make the field nullable and check that it is assigned before using it. While it works, it is not very pretty. And worse, imagine a script A that relies on script B that relies on script C, which itself relies on the script A. This create a circular dependency.

This project have hundreds of file, if we are not careful, it could end up as a web with files dependent on another and another and another. And when we just wanted to test a single script, we end up forced to another 10 mores.
There are various way to tackle this problem but my favorite so far is definitely events.
The events paradigm is composed of two parts:
- The subscriber: It listens to the given event and receive a notification when the event is triggered.
- The emitter: Triggers the event. All the subscriber will receive the notification and handle the event as they wish.
To better understand events, let's make an analogy. As a user on social networks, you follow your favorite artist. When they publish a new art or song, you receive the notification because you are subscribed. If one day you decide to unsubscribe, you won't receive anything the notification if a new song is published. But other followers will! And both the artist and you will be able to keep living your life. And the best part: you can follow them again whenever you want!

Unity has its own delegate for events, called UnityEvent
which has a nice editor, useful for scripts. So we are using it!
As mentioned before, to avoid dependencies, all the scripts will create a single dependency with a class called EventManager
(in EventManager.cs). This class has multiple available static methods (which in fact call a Singleton).
public static void AddListener(string eventName, UnityAction listener) // subscribe to the non-typed event
public static void AddListener(string eventName, UnityAction<object> listener) // subscribe to the typed event
and its counter part
// Do nothing if there is no eventName/listener
public static void RemoveListener(string eventName, UnityAction listener)
public static void RemoveListener(string eventName, UnityAction<object> listener)
These methods are used to subscribe to the given eventName
. An eventName can have as many listeners as needed. There is an overload to pass additional parameters. You may have noticed the second parameter is of type UnityAction
it is of type object
. This is a delegate for functions of type void ()
and void (object)
. The object
type is a lazy type. Basically, any class can be passed as a parameter of it. To then get the passed object as a parameter, simply cast it as (MyClass)obj
. This may be dangerous as someone may not pass the expected type so we will see below how to properly do it.
When you want to trigger your events, use the following methods:
public static void TriggerEvent(string eventName) // Triggers all the non-typed events of name "eventName"
public static void TriggerEvent(string eventName, object data) // Triggers all the typed events of name "eventName"
⚠️ Depending on the overload you chose, the other type won't be triggered. The typed and non-typed events are distinct.
You may have noticed that the event name is a string. However, since it is a string, you may do some typo or want to change the event name at some point without having to change it for all the scripts.
That is why that we have a class called Constants
in Constants.cs that have all the name constants of the events. It contains the sub-classes Events
and TypedEvents
with many constant string.
Hence, instead of writting
TriggerEvent("OnPause")
you can do
TriggerEvent(Constants.Events.OnPause)
It is way more explicit, avoid typos and, on some IDE, you can CTRL+LCLICK on the constant name and have a pop-up with all the scripts that refenrece this constant. Useful to see which scripts listen/trigger the given event. If you see an event name hardcoded as a string, this is because it hasn't been migrated yet. So if you have time to change it, please do so.
TLDR: Do use constants as done above.
Here is a simple case to illustrate the usage.
MyListener.cs
public class MyListener : MonoBehaviour
{
// we subscribe when the gameobject becomes "Active"
private void OnEnable()
{
// notice here that we don't put parenthesis: we don't want to call the method, just reference it
EventManager.AddListener("myEvent", OnEventTriggered)
}
// we unsubscribe when it becomes "Inactive
// because otherwise, it will keep listening and do some actions we may not want to do in this state (such as shooting?)
private void OnDisable()
{
EventManager.RemoveListener("myEvent", OnEventTriggered)
}
public void OnEventTriggered()
{
Debug.Log("The event was triggered!!!")
}
}
MyEmitter.cs
public class MyEmitter : MonoBehaviour
{
private void Start()
{
EventManager.TriggerEvent("myEvent")
}
}
Output
MyListener: The event was triggered!!!
Let's keep the previous classes defined aboved and add a new one.
MyTypedListener.cs
public class MyTypedListener : MonoBehaviour
{
private void OnEnable()
{
// notice how here it the same as MyListener.cs even though it is a typed event
EventManager.AddListener("myTypedEvent", OnEventTriggered)
}
private void OnDisable()
{
// notice how here it the same as MyListener.cs even though it is a typed event
EventManager.RemoveListener("myTypedEvent", OnEventTriggered)
}
// the object parameter is what makes it a typed event
public void OnEventTriggered(object data)
{
// here we safely cast the object type to int or skip if its not an integer
if (data is int number)
Debug.Log("The event contains " + number + " !!!")
// same here but as a List
if (data is List list)
Debug.Log("The event contains a list of size " + list.Length + " !!!")
// we could add an "else" section and throw an Exception if it is not the expected type.
}
}
MyEmitter.cs
public class MyEmitter : MonoBehaviour
{
private void Start()
{
EventManager.TriggerEvent("myEvent") // 1 listener
EventManager.TriggerEvent("myEvent", 86) // 0 listener for this typed event
EventManager.TriggerEvent("myTypedEvent") // 0 listener for this non-typed event
EventManager.TriggerEvent("myTypedEvent", 86) // 1 listener
EventManager.TriggerEvent("myTypedEvent", new List()) // 1 listener
EventManager.TriggerEvent("myTypedEvent", null) // 1 listener. This doesn't print anything as it is not of type int nor List but we do
enter in the method.
EventManager.AddListener("myEvent", () => Debug.Log("Second Listener!) // Add a second listener with a lambda function
EventManager.TriggerEvent("myEvent") // 2 listener
}
}
Output
MyListener: The event was triggered!!!
MyTypedListener: The event contains 86 !!!
MyTypedListener: The event contains a list of size 0 !!!")
MyListener: The event was triggered!!!
MyEmitter: Second Listener!