Mitigating Dependencies in Unity with Events
Learning about events can be a bit of revelation, but conceptually it can be tricky to get your head around, particularly if you’re still fairly early in your Unity career (as I am). I think the main way I tend to understand how these work is that calling an event is like making a radio broadcast within your game: one scripts sends out a message, and other scripts can ‘listen’ in for that message.
Fundamentally, this means the script calling the event doesn’t necessarily know which scripts, if any, will be listening in, and since there’s no direct reference to an instance of another script/class (an object) there’s no dependency on that reference. For instance, let’s say one of our classes tells some other classes when to start/setup:
Admittedly we could/should put in some null reference checks or some other exception handling here, but if we run this now and one of these references hasn’t been provided (in this case in the Unity inspector) we’ll run into some errors. This script is currently dependent on these references being valid in order for it to continue.
Using an event, however, will allow this class to broadcast a message for the FeedbackController and the AudioController to listen for, even without strictly knowing about either of those classes. Whether they receive the message or not, and whether they do anything with the information, is of little concern to this here GameLogic class.
I tend to create a dedicated class to store the events I’m using, and create a static reference to it. I appreciate Singleton patterns can get overused, but in this case I can be certain there’ll only ever need to be one of these in the scene for other objects to make use of. Let’s set up a class for this, as well as a simple event to let us set up these controllers:
After creating that static reference, other scripts in the game can now access this instance just by declaring GameEvents.current (followed by the method they want to call or the event they want to listen to). The ‘public event Action’ declaration here is creating the event itself, which is what the AudioController and FeedbackController will listen for. The public method, on the other hand, is what we’ll be using to actually call this event as long as the event is not null (i.e. ensuring it has at least one listener).
So for this example, both the AudioController and FeedbackController will need to say ensure their respective Setup methods are listening to the setupControllers event:
Now all the GameLogic script needs to do is call GameEvents.current.SetupControllers() and, without any direct reference, the other controller classes will know to call their private Setup methods.
Benefits
One of the main benefits of this sort of system is that it allows us to set up a more ‘modular’ system where we can more easily drag, drop, add, and remove components to the game without causing these errors. We can end up with a system where the player doesn’t know the enemies exist, nor the enemies that the player exists, and so there’s no logic which relies on a direct reference between them. At the very least this can drastically reduce the chances of us getting NullReferenceExceptions at runtime.
Another great benefit of this system is how scalable it is. Let’s say I need to tell every instance of an enemy in the scene that a player’s entered an area. I’d need to, at least temporarily, have a direct reference to each one of these enemies and tell them one-by-one the same information. But with an event system, I can just broadcast this information and every enemy that’s listening for it will know what to do, regardless of whether there are 10 enemies or a 100.
(Important Note!)
An important note on having objects listen to events: if the object is likely to get destroyed during the game (for instance one of these enemies), it’s important that we unsubscribe from any events it was previously listening to. From my understanding this is just something that we need to remember to do, so I’d recommend using Unity’s OnDestroy method and an ‘UnlistenToEvents’ method (or equivalent) to handle this in that event:
This will prevent an event from attempting to broadcast a message to a listener which no longer exists.
***
Thinking in Terms of Events
To really make the most of events in Unity/game programming, it could be worthwhile thinking about exactly what ‘events’ your game consists of. What events change or determine the game state? What messages are there that multiple script might want to know about? Some which come to mind for me are:
- When the player dies (‘OnPlayerDeath’).
- When the player enters an area (‘OnPlayerEnter’).
- When the player takes damage (‘OnPlayerDamaged’).
- When the player enters collects a powerup (‘OnPowerupCollected’).
- When an enemy is destroyed(‘OnEnemyDestroy’).
- When a timer starts or finishes (‘OnTimerStart’ and ‘OnTimerFinish’).
These can lead to a remarkably different way of architecting your games, and are definitely worth a try if you like the sound of what I’ve described in this article and feel like you’re ready to go, if not ‘make the switch’ from GetComponent calls and direct references to an event system.
***
I’ll try not to make this article any longer, but will aim to continue this in a subsequent one 😉 Particularly as we haven’t yet touched on one of my favourite parts of this topic: creating custom UnityEvents. These can allow us to broadcast information, such a boolean, a string, or even a struct. Needless to say, these can really open up a whole world of opportunities when it comes to developing our games.