How to use events to implement a messaging system in Unity/C#

Let’s see how to use C# events to broadcast messages and decouple our game systems!

🚀 Find all of my Unity tutorials on Github!

This article is also available on Medium.

This tutorial is available either in video format or in text format – see below 🙂

Why events are a neat trick for games

As I’ve discussed in other articles, C# events are a very powerful tool that is great for making more robust projects. In particular, they reduce the entanglement between the various systems in your project and help with decoupling.

Events are a way of maintaining communication between your game systems while keeping a nice separation of concerns: everyone does their own thing, but they can send messages to the others and be part of this big ecosystem.

It’s a great way of having your components be more autonomous and handle their logic logically but still be well incorporating in a larger project.

They’re also great in terms of collaborative development because they allow for programmers to chop down the work in several tasks and treat each independently without having to worry about null references.

The big picture

Unity provides us with built-in events and actions, via the UnityEngine.Events package. By using UnityEvents and UnityActions, you can very easily define various triggers and matching callbacks to have some emitters communicate with receivers and provoke reactions on their end.

In this tutorial, we’ll start from an official Unity tutorial on how to build a basic messaging system and improve it gradually to understand how this programming scheme creates a really easy-to-use and maintainable system for communication in video games.

So – are you ready? Then, let’s dive in! 🙂

Step 1: Getting the basic messaging system

This time, we’re not going to start from a blank slate. Instead, let’s take a look at this official Unity tutorial from the April 2015 Live Training on how to make a simple messaging system. As you can see, this page presents you with a great video and then the code that is written during this session.

That’s the code we’ll use as a base for our tutorial. So go ahead and copy the EventManager class in your own Unity project!

Of course, if you want to know all the little details, you should definitely check out the Unity tutorial in full. But I’ll still give an overview of how this script works 🙂

Defining a singleton

At the beginning of the class, you’ll see that you have two important static variables: the eventManager and the instance. Together, these implement the Singleton pattern.

This OOP pattern is a way of insuring that you have only one instance of a given script in your scene. The idea is to create a getter that does one of two things:

  • if you haven’t created your instance yet, then you’ll create, initialise and assign it to the singleton variable
  • otherwise, you’ll directly access this singleton variable to make sure you reference the instance created previously

This way, you know that you have a single copy of your script in your scene and that every time you get it, you’ll access the exact same object. It also provides a global access point to this instance (even if we won’t be using this feature today).

This pattern is sometimes considered bad practice because it has this “global variable” philosophy and because it may not be obvious to the systems that interact with the script that they’re always touching on the same instance. But good data encapsulation and proper accessors can get rid of most difficulties, as well-explained in this Refactoring Guru post 🙂

TL;DR: here, we’ll have exactly one instance of the EventManager script in each scene, no more, no less!

Storing our events

The EventManager uses a dictionary, the eventDictionary, to hold all the events that exist and can be triggered in this scene. It matches each event to a unique string name: that’s the key that the emitter will use to trigger this event and that the receivers will use to subscribe/unsubscribe.

We’ll see very soon that events are registered in this dictionary when there is at least one listener for them. Also, we have to remember to initialise this dictionary in the Init() method to avoid any null references when we add items in it later on.

The meat of the code: listening to and triggering events!

The most important part of this class are the three functions at the bottom: StartListening(), StopListening() and TriggerEvent().

The XListening() methods will be used by the receivers to subscribe to or unsubscribe from a given event. They’ll basically add or remove a listener that automatically runs a specific callback function whenever the receiver gets the associated event.

In the StartListening() method, we check to see if we already have registered the given event. If we have, then we simply create a listener on it; else, we first create the UnityEvent, then create the listener and add it to our dictionary. It’s important to point out that, thanks to that logic, we don’t have any redundancy in our events. In other words, an event called “Event number one” will always refer to the same object (and so the emitter and the receivers will all use the same object).

Similarly, the StopListening() method checks to see if this event exists and, if it does, it simply removes the listener to “disconnect” this receiver.

Finally, the TriggerEvent() method will invoke the event if it finds it in the dictionary (which will automatically cause all the listeners to react). If the event hasn’t been registered, then the EventManager simply ignores the request from the emitter.

Note that these three methods are static: this means that we’ll be able to call them directly on the EventManager class itself rather than go through an instance.

Step 2: Creating a test emitter and receiver

To see how we can use this tool, let’s create a very basic emitter that sends out a “Test” event; and a matching receiver that debugs something when it gets this event.

We’ll make the emitter fire the event whenever we click the left-mouse button. So this gives us a very simple TestEmitter script:

For the receiver, we need to know when to start and stop listening. What we want is for the object that has this component to add its listener as soon as it’s active and remove it whenever it becomes inactive.

Luckily, Unity has those exact hooks all ready for us: it’s the OnEnable() and OnDisable() built-in entry points. Meaning that all we have to do is subscribe to the event in the OnEnable() and unsubscribe from it in the OnDisable().

But a harder question is: how do you define the callback function to run whenever the listener receives the event?

We can do this either explicitly or implicitly. We’ll start with an explicit definition to properly understand what objects we’re manipulating.

“Reacting to an event” actually translates to “running a UnityAction” that has been associated with the event in the listener. So here’s what we need to do:

  • first, import the UnityEngine.Events package so we can define a UnityAction variable
  • then, create our UnityAction variable
  • then, create the method that this UnityAction will run
  • finally, initialise the variable by passing in the proper method

Here’s the corresponding script (to put in a TestListener.cs file):

Note: a convention I like to use with events and callbacks is to always prefix my callbacks with the “on” prefix and then have the name match the name of the event. This way, I immediately know it’s a callback, and what event it is for 🙂

Be careful to use the exact same string name for your event both in the emitter and listener script (here: “Test”): it is case-sensitive and must be an accurate match.

Let’s test this out!

Go back into Unity and create a new empty game object to put our scripts on (otherwise, those MonoBehaviours will simply stay still in our Assets folder!). For this first test, we’ll simply put our three scripts on the same object: so add to it the EventManager, the TestEmitter and the TestListener. You can also rename this game object to something like “EventManager”. Now, run the game and try left-clicking. You should get a little “received test!” debug in your console.

So, what happened? Well, when you clicked the left-mouse button, the TestEmitter caught this input and triggered the “Test” event; then, the TestListener reacted to this “Test” event and ran its associated UnityAction which called the OnTest() callback function and printed the debug.

Step 3: Adding some visuals

To help us understand how objects in the same scene can interact thanks to this messaging system, let’s add some 2D sprites and imagine a very (very!) simple game where we have a hero on the left and some enemies on the right; the hero can send messages to the enemies.

For now, I’ll just put a black square on the left for the Hero and a red square on the right for the Enemy.

Then, let’s remove the TestEmitter and TestListener from the “EventManager” game object (but leave the EventManager on it!), and move those to the Hero and the Enemy respectively: so the Hero has the TestEmitter and the Enemy has the TestListener.

If you run the game again and click the left-mouse button, you’ll see it still works perfectly: that’s because our system doesn’t care where the emitter and listeners are – they can be on different objects, they’ll continue to communicate!

Step 4: Broadcasting to multiple receivers at once

Another key feature of this messaging system is that it actually broadcasts to all the receivers that subscribed to the event. This means that whenever the event is emitted, all the listeners that are interested in it will react directly and at the same time.

To see this in action, let’s do a little modification in our TestListener class and inject the name of the game object in the debug:

With this indication, we’ll be able to differentiate between the various receivers and check that each does receive and react to the event!

Now, let’s update our scene. We’ll just copy our Enemy and create two new ones that we’ll put above and below. Those are automatically named “Enemy (1)” and “Enemy (2)” so we’ll clearly see the difference in our debugs.

If you restart the game and left-click, you’ll now have 3 debugs! There is still only one emitter, and it triggered only once, but each enemy received it and reacted to it at the same time 🙂

Another important point is that, here, the emitter defines when the reaction happens, but the receiver defines how it happens. You can see this clearly by creating another listener script (for example OtherTestListener.cs) and slightly changing the callback function:

Then, put this script on one of the 3 enemies (I also changed its colour to blue to distinguish it from the others); this time, you’ll get 2 “simple” debugs (from the two red enemies) and one “special” debug (from the blue enemy):

Step 5: Emitting/Listening to multiple events

Also, our system is of course not limited to a single event per emitter or per receiver! Depending on the context, our Hero could trigger several events, and our enemies might catch one or more of those to react accordingly.

Let’s say that our Hero can tell the blue enemy to change colour: from blue to green, and green to blue, and blue to green,… and so on. We’ll trigger this new event, “ChangeColor”, when we click the right mouse button.

On the emitter side, it’s still pretty easy:

On the receiver side – we want to update our brand new OtherTestListener class and subscribe to this new event. (Naturally, we’ll also have to unsubscribe from it when we get disabled)

For the callback function, let’s see how to define it implicitly this time.

Rather than creating a UnityAction-typed variable and passing it the method to run, it’s often quicker and simpler to just put the callback method to run directly at the listener creation hook. As long as you have a void function (i.e. that returns nothing) and that has no parameters, it can be swapped in our StartListening() call directly instead of a basic UnityAction!

In the callback function, I’m getting a reference to the SpriteRenderer component on my enemy and I’m changing its colour to switch between blue and green.

To test this out, don’t forget to assign the SpriteRenderer in the OtherTestListener component’s slot!

Then, run the game and try left-clicking or right-clicking. You’ll see that the debugs remain the same (two “simple” ones and one “special” one) and that when you click the right mouse button, the blue enemy changes colour again and again.

Step 6: Passing in data with our events

The last thing I want to discuss in this tutorial is how we can convey data through our events. So far, we’ve only looked at basic events – those simply ask the receiver to do something but they don’t share any particular option, parameter or data with the callback function.

This can make sense if you want your listeners to be in full control but, oftentimes, you’d prefer the emitter to have a say in what’s to do. For example, take our “ChangeColor” event. At the moment, the enemy is in charge of choosing what colours to change to whenever it receives the event. This might not be the best if you think of this event as an action that the Hero does on the enemy.

What you’d want here is rather for the Hero to send the colour to the enemy as well as the “order” to change colour; that way, the emitter will determine both the time and the parameters of the reaction and you’ll be able to prepare the future state of the game all on the sender side. Because as you probably know: better centralisation of logic and data ownership are pretty important parts of any programming project!

To allow for our events to carry along data, we need to upgrade our EventManager. In particular, we can’t just use simple UnityEvents for that. Instead, we’ll take advantage of the fact that the UnityEvent type is actually a generic type and further specify it so it can take in one data parameter.

This is done by declaring our own class: the TypedEvent class, that just implements the generic type. In order to carry just about anything, we’ll rely on object-typed data: this C# “lazy type” is a way of sending whichever type of variable you want through and then restore it to its real value at the other end via casting. These transform processes are called boxing (from “real type” to object) and unboxing (from object to “real type”).

So our TypedEvent class will be declared as:

[System.Serializable]
public class TypedEvent : UnityEvent<object> { }

(You can put this line at the top of the EventManager.cs file, just below the imports for example.)

We need to make sure that this class is serialisable so that our EventManager can use it. And so, in this EventManager class, we’ll just “duplicate” what we have for basic events and do the exact same for TypedEvents. Instead of using UnityEvents, we’ll use UnityEvent<object>s (i.e. TypedEvents); and instead of using UnityActions, we’ll use UnityAction<object>s:

By providing a second prototype for our StartListening(), StopListening() and TriggerEvent() methods, we’re making it completely transparent for programmers to switch from one to the other: basically, you just have to add an object parameter in your callback function and pass in some data when you trigger the event, and the whole thing will automatically be routed to the “typed” versions!

Here is an example with our “ChangeColor” event. On the emitter side, we just add some data when we send the event:

And for the receiver, we add the parameter to the OnChangeColor() callback method, unbox the value and use it in our process.

Now, our emitter completely controls the “change color process” from end to end, even though the workers responsible for the different parts of this process are scattered throughout the scene!

Conclusion

Events are an amazingly powerful tool that can be used to quickly build a simple messaging system. Here, we’ve seen how to easily broadcast triggers in our scene and have multiple receivers react at the same time, each in their own way. Also, we’ve seen that an emitter can send different events depending on the context and that, similarly, one receiver can listen to multiple events at once.

Listeners can behave in various unique ways: you could think of one receiver being a UI manager, another handling the sounds, a third one being in charge of AI control…

This kind of system is great for decoupling your systems and avoiding tight dependencies. That can be particularly useful for test scenarios. If instead of having classical Unity references (via GetComponent() for example) you rely on a messaging system like this one, then if one of the receiver isn’t enabled – because you’re not interested in studying it at that moment -, it won’t cause any null reference. You simply won’t get its callback reaction, and that’s it: no need to re-import half your scene until all the “missing references” go away! 😉

I hope you enjoyed this quick Unity tutorial and the dual video/text versions. Feel free to react in the comments and tell me if you like this new format — and of course, go ahead and share your ideas for future topics you’d like me to make Unity tutorials on! 🙂

Leave a Reply

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