Home » Events in Unity

Events in Unity

Events in Unity

Most programmers are familiar with the concept of events. Considering how popular Unity is, it’s still surprisingly difficult to find a single place of information on events applied in Unity environment.

I’ve met many individuals both on the internet and at work who either are ignorant of the benefits of events or simply don’t know how to use them in Unity. Many of the blog posts on the topic either provide a quick peek on the subject or delve to custom implementations with particular needs. Moreover, some of the information is simply wrong or at least bad advice.

In this article, I’m trying to address this lack of information, providing a solid place to learn the essentials of event-oriented development in Unity.

What Are Events?

Think of an alarm clock. If you had to program a behavior of an alarm clock, how would you implement it? The main feature of it would be to launch the alarm when it’s a right time. Now, ignoring all the technical things, you don’t want to check the time each second, each minute, or each frame. You want someone to inform you when it is the right time so that you can ring the bell. That’s an event; an event is a notification when something happens.

If you are working in Unity, you have at least three possible implementations of events in your tool bag: native C# events, Unity Events, and the legacy messaging system. Only the first two of these are worth using.

Inter-Component Communication – The Default Way

When we start working in Unity for the very first time, we need to figure out how your scripts can communicate with each other. Let’s take a look at the default way.

First, we need a reference to the class (setting static classes aside). If the classes inherit MonoBehaviour, they are used as components of a Unity object (most often GameObject). This allows us to use methods for finding components from the object in question. It also enables the references via serialized fields as well as the methods for finding game objects by type, name, or tag from the scene. When we have the reference to the other script (instance), we can call it’s public methods. The code might look something like this:

public class MyClass
{
    public OtherComponent other;

    void Start()
    {
        if (other == null)
        {
            other = FindObjectOfType<OtherComponent>();
        }
    }

    void Update()
    {
        if (someCondition)
        {
            other.SomePublicMethod();
        }
    }
}

In principle, there is nothing wrong with this – Assuming that the responsibility of the script is to control the other component. But not all scripts manage other components. Suppose that the changes in “MyClass” have an effect to the state of the “OtherComponent”. Now we would also need to do similar monitoring in the “OtherComponent”. Soon we will find ourselves with a tightly coupled code base, where all classes depend upon each other; try to remove or modify one and you have to alter the others as well. Another implication of this is that there will be no clear assignments of tasks for the classes, let alone a structure of hierarchy.

Encapsulation and Decoupling

The key factor in building and designing an application is to decide how to divide tasks between the classes. Many speak about the separation of concerns. Some assert that all modules of a program should have only a single responsibility. Yet most agree that encapsulating and decoupling entities from each other is usually a good thing. It helps the programmer to understand the code, split greater tasks into smaller, and to isolate the side effects (and hence also bugs) into smaller areas of the program. For example, it’s often a good practice to keep the visuals separated from the logic (see e.g. MVC and MVP patterns). This allows us the refactor one without affecting the other.

So, to loosen-up our dependencies and to improve the responsibilities of our classes, we need to change something in the code above. Supposing that we had events, we could define an event for “someCondition” and launch it when the condition is matched. Now, instead of calling the public method of the “OtherComponent”, we could register to this event and define the reaction to it in a private method of the “OtherComponent”. Consequently, neither of the components would be controlling the other, and the coupling of the objects would be only a matter of reference.

Let’s take another, more concrete, example. Suppose that we have an AI component and a health component for all the monsters in our game. Without the events, we would have to monitor the state of the health component in the AI component like this:

public Health health;

void Update()
{
    if (health.State == HealthState.Something)
    {
        // React to the state change
    }
}

Now we are constantly monitoring the state of the other component, which we should not do, as argued above. By building upon the power of the events, we could refactor the AI component like this:

public Health health;

void Awake()
{
    health.StateChanged += OnHealthStateChanged; 
}

private void OnHealthStateChanged(HealthState state)
{
     // React to the health change
}

In order to build decoupled and well encapsulated components, we would want to keep most of the functionality of the component private concern of the component and hidden to the others so that the component work in an autonomic manner. We’ll also want to keep all the dependencies between the components as loose as possible. Events help us to reach both of these goals.

Publish and Subscribe

In C#, events are an implementation of the publish-subscribe pattern, which is a variation of the observer pattern. The key idea is that the publisher (or sender) of the event does not know about the subscribers (or receivers).

The publisher defines the event and handles the launching of the event. The subscriber registers to the event and defines what it wishes to happen when the event is dispatched. This is the event callback. And there can be many of those without that they have to know about each other.

Diagram about the Publisher-Subscriber Pattern

The event callback is a delegate instance, which in practice means that the method is used like a variable (the closest thing in C++ would be a function pointer). Delegates can be defined as fields or locally inside a method. Delegates hold a reference to the method and hence can be treated as variables.

If you are new to these topics, don’t worry. Just think the direct method calls as mail or email. You have to know the address, right? Now think of a newspaper or a forum post. Instead of having to know the address for each individual reader, the writer simply assumes that there may be some (or none) readers for the message. Afterwards, the text can be used in many ways, without the author having any control on how it is used.

Do you now see what the advantages are? In the context of text, we gain cumulative information that’s easy to share among a huge amount of people. In the context of programming, we achieve encapsulation and decoupling of systems. That, in turn, leads to a code base that can be reused and components that can be swapped on demand. By using events, we can create autonomic components that handle their own business without an intervention from other components. They do things and notify others about it, leaving the imperative parts to some other guy who’s specific task that is (if it is).

OK then, let’s finally proceed to how the events are used in practice. First, let’s take a look into native C# events.

Defining and Using the C# Events

In C#, there is a built-in event system that is reliable, performant, and fairly easy to use. The event system uses delegates for passing over the event callbacks. A delegate can be defined like this:

delegate void CustomEventDelegate(int x);

An event is defined like this:

event CustomEventDelegate CustomEvent;

Since the event is using the delegate, we have to define both, which is a bit annoying. There is a way around this, but more on that later. First, let’s take a look on how the events can be used. First of all, subscribing to the event is done like this:

CustomEvent += OnCustomEvent;

On the right hand side, we have the delegate. Registering a named method on the event, treats the method as a delegate. In the early versions of C#, using a named method was actually the only way to initialize a delegate.

Unsubscribing from the event is done like this:

CustomEvent -= OnCustomEvent;

When the event is dispatched, the event delegate launches all the registered delegates. This is what a multicast delegate means in practice. All C# delegates are multicast delegates. Therefore, any delegate can be appended and deducted with + and - operators. Dispatching (or raising/calling) the event can be done either like a method (CustomEvent()) or explicitly by calling CustomEvent.Invoke(arguments). Delegates can also be assigned or reassigned using =operator, like this:

CustomEvent = AnotherEventDelegate;

Unlike other delegates, event delegates can be assigned only inside the class the event belongs to. This is because, behind the scenes, the compiler creates public and private wrappers for the field. Moreover, the compiler allows only the defining class to rise the event.

These are the reasons why we have events in the first place: they cannot be redefined from outside, yet they can be accessed and used by others. Note that if the event delegate is redefined after the subscribers have registered to it, all the previous event callbacks are lost!

Generic Delegates

Above I mentioned that both the event and the delegate has to be defined before they can be used. But it’s actually the same thing with all non-generic delegates:

MyDelegate myDelegate;
delegate void MyDelegate(int x);

The definition of the delegate and the reference to the delegate instance are both logically and technically different things, but it’s still an annoyance to be forced to write  boiler-plate code over and over again.

When it comes to events, this is cumbersome because the delegate is a part of the implementation of the event system, which we, as the users of the system, are not very interested in. We only want to access the event. Fortunately, this can be overcome with the help of generics.

Funcand Actionare generic delegates and can be used like non-generic delegates. It’s therefore possible to simply add the keyword eventbefore the delegate definition:

event Action<int> CustomEvent;

This is actually all we need, because we are using a generic delegate, which is already defined in the Systemnamespace.

Using generics is all well and fine. There are some downsides to this, though. The most severe is that if we decide to change the parameters of the delegate, we have to change the delegate signature also. This has to be done everywhere the event is used.

Consider that our requirements change: instead of passing an int, we also have to pass a string. Now we need to change the delegate to from Action<int>to Action<int, string>, which will break all the event delegates that are subscribing to this event.

This is where EventHandlersand EventArgscome into the scene.

EventHandler and EventArgs

EventHandler(found in the Systemnamespace, like Funcand Action) is a generic delegate that only accepts types that inherit EventArgs. EventArgsclass always has a reference to the publisher of the event (as type of object). It also can contain custom arguments.

If we simply want to notify that something happened and don’t need to pass any data about the event, we define the event like this:

event EventHandler CustomEvent;

Subscribing to an event using the EventHandlerdelegate is not any different from subscribing to any other event. Yet, you should note that the delegate has two required arguments: the sender of type objectand an empty EventArgsclass.

Calling the event is done like this:

CustomEvent(this, EventArgs.Empty);

And defining the event callback as a named method like this:

void OnCustomEvent(object sender, EventArgs args)
{
    /* implementation */
}

You might wonder, what’s the point in passing an empty class. But actually that is the important part. As mentioned above, if the requirements change, we have to change the signature of the event delegate. Yet, if we are using EventArgs, we can simply create a custom EventArgs-derivative class:

public class CustomEventArgs : EventArgs
{
    public readonly string myString;
    public readonly int myInt;

    public CustomEventArgs(string myString, int myInt)
    {
        this.myString = myString;
        this.myInt = myInt;
    }
}

Calling the event using EventHandlerwith data is done like this:

CustomEvent(this, new CustomEventArgs(myString, myInt));

Now, we can use the arguments in some classes without having to redefine the callbacks of all the subscribers that were created before.

As a downside, if we use any arguments, we have to create custom EventArgsclasses for each and every event that we use. We could create generic EventArgsclasses, but then we would fall back to the requirement of having to redefine the delegate signature when the arguments change in number or in type.

There is also another disadvantage in using EventHandlerdelegates. Did you notice the keyword newthere? Yes, we are actually creating new instances of EventArgseach time the event launches. That will generate some garbage. Moreover, this is actually the preferred .NET pattern, of which there is no reasonable way around.

Most of the time small amounts of garbage are not an issue, but if the event could be launched frequently (like every frame), we might want to avoid that. Note here that passing EventArgs.Emptyre-uses a static empty instance that is defined in the EventArgsclass. Therefore it does not generate garbage.

In principle, one could create the EventArgsclass only once and cache it for later usage. But then the fields could not be readonly and they couldn’t be protected. It is also quite likely that we really need multiple instances of the same class. How do we handle that? Let alone multi-threading? In performance-heavy cases, we might be better off using either custom or generic delegates instead.

When and Where to Subscribe/Unsubscribe

In order to prevent missing reference exceptions when using native C# events in Unity, you need to be clear when to subscribe to and unsubscribe from events. You can start with simple rules:

  • Subscribing to an event should always be done before the event can be launched.
  • Unsubscribing should be in proportion to subscribing.
  • At the latest, we should unsubscribe before destroying the subscriber.

The first two rules are self-evident. The last one is based on the fact that the publisher of the event has a reference to the subscriber via the delegate. Therefore the subscriber cannot be disposed if it has not unsubscribed from the event.

In Unity, a good starting point would be to use OnEnable()for subscribing to the event and OnDisable()for unsubscribing from it. In many cases Start()or Awake()and OnDestroy()may be preferred.

Most often I’m using custom initialization functions and events to call the registration methods when, for example, all the components of a game object have been initialized. This is because the execution order of OnEnable()and Awake()callbacks varies; the Awake()callback of component A will be called before the OnEnable()of the component, but it may not be called before the OnEnable()callback of component B.

The registration can also be much more dynamic. Let’s say we have a list of objects we want to listen to, but the list is constantly changing. A mess, right? Actually, not at all. We simply subscribe to the event of the target object when we add it to the collection and unsubscribe when we remove it.

Anonymous Methods

Now, I’d like to deviate from events and discuss briefly the topic of anonymous methods, because there is an important thing to note when you are using them to subscribe to an event.

Let’s assume that we need to pass a delegate that sums two integers together. If we had to use C# version 1.0, we would have to define the delegate with a named (normal) method like this:

delegate int SumDelegate(int x, int y);

static int Sum(int x, int y)
{
    return x + y;
}

Now, the delegate instance could be created like this:

SumDelegate sumDelegateInstance = new SumDelegate(Sum);

This delegate instance we can then pass on to a function or an event or whatever we need to do with it. Quite cumbersome. Fortunately C# 2.0 introduced anonymous methods. Using C# 2.0, we can now leave out the named method all together and define the delegate instance like this:

SumDelegate anonymousMethod = delegate(int x, int y) { return x + y; };

Since C# 3.0, it’s been preferred to use labda expressions, which are more concise. Originally borrowed from logics, labda calculus is a simple syntax that leaves out all the non-essential. Using a labda expression (or arrow notation), the sum function could be described simply as: (x, y) -> x + y.

In C#, we don’t need much more than that: SumDelegate labda = (x, y) => x + y; Note that the arrow is a bit different. If we also had generics (post C# 3.5), as we do, we could use a generic delegate Func<int, int, int>, eliminating also the need to use the custom SumDelegate. Using a generic delegate with labda expression, we can define the sum function as Func<int, int, int> Sum = (x, y) => x + y;.

Using Anonymous Methods with Events

Since we all now know what the anonymous methods labdas are, let’s continue to the actual reason why I’m raising the topic here.

Labdas are an easy way to define event delegates. For instance, if we want to ensure that an event cannot be null (in other words that it has at least one delegate registered to it), we can ensure it by adding an empty delegate to the event when we define it:

event EventHandler MyEvent = (sender, args) => { };

We can use labdas also when we subscribe to the event, like this:

MyEvent += (sender, args) => { };

Nice! No need to define named methods with boring “OnMyEvent”-like names, right? In some cases it’s just so. But what if we’d like to unsubscribe from the event?  – Well, it cannot be done, because we don’t have a reference to the delegate we just used!

To be able to unsubscribe from the event, we need a reference to the delegate. If we subscribe in Awake()and would like to subscribe in OnDestroy(), we need a class scope reference. The delegate must therefore be defined either as a field variable like this: EventHandler onMyEventor by using a named method. In most cases, if I have to unsubscribe from an event, I prefer the latter approach.

The important thing to consider here is that unsubscribing is not always required, if the subscriber always outlives the publisher. However, if this can be the other way around, we should make sure to unsubscribe before destroying the subscriber. Fail to do that, and the publisher will keep the subscriber alive (in C# side) and the delegate still persists, although the Unity Object it belonged was destroyed.

So, in short: by all means, use labdas, but know what you are doing.

Static Events

In general, events can be set into two categories: global and local. What I call local events here, are events that we need to access via instance reference. Global events, on the other hand, are accessible to everyone. The difference in syntax is only that we add the keyword staticin the definition:

public static event MyEvent;

We can now access the event directly like a static method. This means that the event is used in a bit different way. Let’s say we have an event that launches when the object is destroyed:

public event EventHandler Destroyed;

private void OnDestroy()
{
    if (Destroyed != null)
    {
        Destroyed(this, EventArgs.Empty);
    }
}

Turning the event static won’t change much on the publisher’s end. The subscriber, however, has to handle the identity of the sender, which it didn’t have to do before:

private void OnDestroyed(object sender, EventArgs args)
{
    var component = sender as Component;
    if (component == componentWeAreWatchingFor)
    {
        // Do things
    }
}

If we didn’t use EventHandlerclass here, we would need to pass the identity of the sender as an argument of the delegate. An important thing to bear in mind when using static events is that they don’t get cleared by the garbage collector. Therefore, we should make sure that the event is properly reset when it needs to (for example when a scene unloads).

OK, enough about the native events. Next, let’s look into the alternatives ways of sending messages in Unity. There are actually two options, of which only one is worth considering.

On the Old Messaging System…

Before Unity Events, there was another built-in messaging system in Unity. Hopefully you have avoided using it.

It uses SendMessage()and BroadcastMessage() for invoking methods between GameObject components. This system is slow and clumsy. It’s not type-safe, because it relies on strings for matching up the methods. Moreover it accepts only a single parameter as an argument.

Simply said: don’t use it. It’s a legacy thing.

What about Unity Events?

Since Unity 4.6 or something, we’ve had Unity Events, which are a lot better than the old messaging system. In some aspects they may be preferable over the native events.

Before comparing these two, let’s first look how the above given example will look like if we used Unity Events:

[SerializeField]
private UnityEvent _destroyed = new UnityEvent();
public UnityEvent Destroyed { get { return _destroyed; } }

private void OnDestroy()
{
    if (Destroyed != null)
    {
        Destroyed.Invoke();
    }
}

The event is set private, because I don’t want anyone outside the class to be able to redefine it. To display the event in the Inspector, we need to serialize it. Unlike public fields in Unity, private fields have to be explicitly marked as serializable with the special[SerializeField]tag. In terms of functionality, this is not necessary: we could also define it public or private without serialization. The difference from the native events is that instead eventkeyword and EventHandlerdelegate we are using UnityEventand UnityAction.

UnityEventclass is used much like EventArgsclass, and we can create our own variations of it. Note that raising the event cannot be done like a method call: we have to explicitly invoke it. More specifically, Invoke is actually a public method defined in the UnityEventclass. Because it’s public, the event can be raised from outside. Subscribing to and unsubscribing from Unity Events is done via AddListener()and RemoveListener()methods.

If we need custom arguments for the event, we have to implement a custom class that inherits UnityEvent:

[System.Serializable]
public class CustomEvent : UnityEvent<T> { };

Here T is the type of our arguments, so in practice it could be, for instance, a string. The class has to be marked serializable or the events won’t show in the Inspector. The body of the class can be left empty.

Unity Events vs Native Events?

So which one should I use: native C# events or Unity Events? Well, it depends. Personally, I prefer the native, but there are at least two reasons to choose Unity Events over native C# events.

First, they can be serialized, which means that they show up in the Inspector. Apart from the built-in methods, we still need to define the custom logic in code, though. But if the scripts are already done, a designer can hook them up to other systems in the Inspector, without having to write any code. This has potential benefits of modularity.

The second reason for choosing Unity Events over the native events is that they use weak references for pointing to the subscribers. With the native events, the references to the subscribers are strong (although there are workarounds). A strong reference has the implication that the event publisher will keep the subscriber alive. This means that the garbage collector cannot clear it from the memory. Failing to unsubscribe from the event properly may cause memory leaking and most likely missing references. Weak references, on the other hand, can and will be automatically cleaned up by the garbage collector when the subscriber is destroyed. For the user’s perspective, this may or may not be desired behavior. In most cases it’s still good practice to explicitly unsubscribe from the events.

So the upside of weak references is that they are safer and can prevent missing or null reference exceptions, if the subscriber is destroyed before unsubscribing from the event. The downside is that when you use editor to assign the subscribers to the events, you won’t get any notifications if the links are broken. In my experience, this happens a lot, especially when the project is worked on different computers. You only need a missing .meta-file or a corruption in the local Unity project’s Library. So the weak references are a double-edged sword.

Performance-wise there are differences between the event systems, but they are quite minor. Something to bear in mind though, is that subscribing to the native events generates some garbage, but dispatching the event does not (when we are not creating new EventArgs). Unity Events, on the other hand, create garbage when they first launch, but not thereafter. Moreover, if there is more than one subscriber, the native events tend to generate more garbage than Unity Events. When it comes to the processor time, launching a native event is somewhat faster than launching a Unity Event. Both are still quite fast.

In general, we should not think the performance too much here, unless the profiler indicates otherwise. In most cases, using any kind of event system leads to far better performance than relying on frequent checks on loops like Update()or coroutines.

The major issue regarding Unity Events is the inability to ensure that the raising of the event is handled where it should be. As denoted above, the Invoke()method of UnityEventclass is public, which means that you can actually call the event from wherever you want. It is possible, for example, to fake a button click by calling GetComponent<Button>().onClick.Invoke()anywhere. Yuck.

A Word about Event Managers

In some cases you might find it preferable that neither the publisher nor the subscriber would know about each other. For instance, we might want to have a bunch of events to be called from anywhere and be received by anyone. Regardless of which one of the above described event systems we are using, this is not possible by default. The subscriber has to know something about the event it’s registering to.

We can overcome this limitation by using a mediator class, an event manager. There are many ways of achieving this, see for example the official tutorial (you might want to consider a type-safe implementation with enum or custom classes to represent the events). The common nominator in these is that the mediator provides an access point for all subscribers and publishers. It stores the events and launches them when asked to do so.

One advantage of using an event manager is that the subscribers and the publishers don’t know about each other and thus be easily changed without changing the code. So, if you need to have drag-and-drop-level modularity, you might want to consider an event manager.

Another benefit of the event manager is that there can be many publishers. If you know that you need to have multiple publishers, you probably need some kind of an event manager. Note that this very same thing can also be a disadvantage, because it’s now impossible to encapsulate the event handling to one place: by definition, anyone can call the event manager to launch an event. Sacrificing the encapsulation like this can lead to messy code. Relying heavily on a generic event manager, may also implicate that the responsibilities between the classes are not well defined.

For instance, suppose that we want to notify an UI element when a monster is spawned in our game. We have at least two valid options: either we register to a static event defined in the class that represents the monster, or we use a monster factory manager class to launch the event. If we need a mediator in this case, I would rather choose the monster factory over an event manager – after all, we don’t want to allow just anyone to send the spawn message, do we?

Resources on the C# events:
https://docs.microsoft.com/en-us/dotnet/standard/events/
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/events/
http://csharpindepth.com/Articles/Chapter2/Events.aspx

Resources on Unity Events:
https://docs.unity3d.com/Manual/UnityEvents.html
https://unity3d.com/learn/tutorials/topics/scripting/events
https://forum.unity.com/threads/unityevent-where-have-you-been-all-my-life.321103/

2 comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.