RTS Interlude #1: Introducing an event system (Unity/C#)

On with our RTS project – in this interlude, we’re going to talk about events!

This article is also available on Medium.

So far in this series, we’ve set the scene for several big features and used a little palette of tools that Unity and C# provide: the new UI system, raycasting, game object instantiation, resources loading, material switching, global variables and basic data encapsulation… There is however something fundamental in video games we haven’t yet talked about: events.

Overall, programs can follow various paradigms either in terms of instructions execution, data layout, components hierarchy and communication, etc. In video games, it is very common to have an update loop that runs continuously (more or less every rendering frame) and checks with all the systems in the game if they need to do or return something. In Unity, this built-in structure is visible through the Update() method that’s available in every MonoBehavior-derived class.

To interact with all those systems and react to user inputs, video games rely on events. Today, we are going to implement a basic event system and use it to improve how we update the game resource texts in the UI when placing a building.

Why use events?

The funny thing about object-oriented programming (or OOP) is that, at its core, it wasn’t meant to be about objects primarily. Its creator, Alan Kay, had very different things in mind for it compared to what it’s become – most famously, he once said:

I invented the term object oriented, and I can tell you that C++ wasn’t what I had in mind.

More specifically, Kay’s background in biology lead him to design a way of programming based on autonomous entities communicating between them via messages. Objects would be individual cells in a big network, properly encapsulated with “local retention”, without all the porosity we usually see in OOP these days. Hence the idea of using events as a tool for this messaging and for keeping objects separate “organisms”.

A big plus when using an event system is that our emitters broadcast their events to all other components at the same time. For example, you could send a single event and have it caught by the UI system, the audio system, a data manager… Virtually all parts of your projects can react to an event and it’s very easy to add or remove those listeners.

All of this brings us to the essential reason event systems are so great: modularity. Whenever your project starts to grow in complexity, you run the risk of having all of your components too entangled together – you basically end up with one big mix of lots of little things that you can’t separate anymore. You don’t have a network of individual organisms anymore but one big organism with a somewhat Frankenstein body. In OOP, this can happen particularly quickly: think of how easy it seems at first to add a reference to our UIManager in a script and call it to update some display on the screen. Although it sounds great at first, it’s actually a pattern that slowly drags your code down because you are tying all of your systems together and creating unnecessary dependencies.

The way an event system handles this issue is by relying on a “fire-and-forget” mechanism. Roughly speaking, this means that when an emitter sends an event, it doesn’t care about whether or not there are listeners to catch it, and if they do catch it properly. Its only job is to send the event. Then, it’s up to the listeners to be up and ready. The advantage of this philosophy is that everything is decoupled and therefore more robust and easy to unit test.

Say you want to check that your mechanics for updating the player health works well. In a naive setting, you’d probably have a reference in your player manager script to your UI system so that whenever the player’s HP are modified, the healthbar shown on the screen shrinks or grows accordingly. The direct consequence of this link is that if you create a new empty scene and bring in your player manager, you’ll have a “missing reference” for your UI manager. Therefore you’ll have to import this one as well. And chances are that this UI manager relies on your global game manager class; so you’ll have to import this class, too. And so on…

Thanks to events, you break the connection between the player manager and the UI system. Instead, your player manager will simply emit an event that can be caught by the UI system if it’s there, or ignored otherwise.

Creating the EventManager class

To kickstart this feature, we can draw inspiration from the official Unity’s live training on how to design a simple messaging system. The EventManager they create throughout the session holds a C# Dictionary of events keyed by names (as strings) and it uses the Singleton pattern I mentioned in a previous tutorial. The instance getter either returns the private _eventManager field if it is already defined, or it first assigns/initializes it and then returns it. This is a way to have a unique version of an object that is initialized just once and then accessed has many times as necessary.

Note: this pattern like many others is brilliantly explained by Bob Nystrom in his “Programming Patterns” book – I really encourage you to take a look if you’re interested in code design and architecture, especially in the field of video games (but not only!).

Just adapting Unity’s training session gives us the following script:

To use this event system, you’d create scripts like these ones, and then put them on an object in your 3D scene:

However you might notice that those events can’t handle any parameter – they just broadcast an event name that can be caught by listeners to trigger a callback… but they don’t convey any data!

Even though we won’t need it today, it will quickly be necessary – so let’s add another Dictionary of custom events that can contain data. We’ll need to create a CustomEvent class for this specific type of event and also a CustomEventData class to easily store the data of our events. This class is used similarly to C unions: we define several fields but we use only one at a time.

This example allows us to create events sending data of the BuildingData type with them.

We can now easily use it in our EventManager:

Cutting the connection between the BuildingPlacer and UIManager

Now that we have a custom event system, we’re ready to increase our modularity by removing some links between scripts. In particular, we currently have a link to the UIManager in the BuildingPlacer class because last time we added some code to update the display of game resources in the UI when placing a building:

Even if both these scripts are on our “GAME” object and should be present together, it’s better to avoid such a harsh entanglement. Instead, we can remove this _uiManager variable and emit events:

And then, we simply add the corresponding listeners in the UIManager class – it’s a good opportunity to change the associated callback functions into private methods to improve our data encapsulation and prefix those callbacks with “On” (it’s a common convention when defining event callbacks):

Conclusion

Today, we prepared an essential feature for inter-scripts communication: a basic customizable event system. We also refactored some of our previous code to benefit from this new tool and improve the modularity of our scripts.

It might not look like much but in terms of game architecture, it is very important to try and separate as much as possible your components. This way, you avoid unnecessary dependencies and you isolate the components – this is particularly interesting when:

  • you want to unit test your assets and scripts, especially because you don’t require the listener to exist: events work in a “fire-and-forget” paradigm where the emitter broadcasts its event but doesn’t need listeners to answer back; so if you’re concerned about one module in your unit test, and even if it fires events, you can only instantiate this part of your project and forget the rest
  • you work with several developers and each work on a different part of the project: thanks to modularity, you can all take care of your piece of code and have well-prepared interfaces/communication systems

Next week, we will implement another crucial feature in any RTS game: the ability to select units by dragging a box around them or clicking directly on one.

5 thoughts on “RTS Interlude #1: Introducing an event system (Unity/C#)”

  1. love the Tutorial. but I need some help with a problem I have after finishing this part.
    everything spawns as it should but it has stopped taking the resources from the values. and it only allows me to build twice but then suddenly stops.
    any answers would be greatly appreciated.

    1. Hi – really glad you like it, thanks! 🙂 I’m sorry to hear you’re having some issues; it’s a bit hard to debug from this info… what could be happening is that:
      1. “everything spawns as it should but it has stopped taking the resources from the values”: perhaps you aren’t processing the “UpdateResourceTexts” event properly. Perhaps it’s misspelled somewhere, or you forgot to hook up some callback function in the UIManager?
      2. “it only allows me to build twice but then suddenly stops”: how much initial resources to you have? how much do your buildings cost? Remember that we’ve got some logic in the tutorial to automatically cancel placement whenever you don’t have enough resources to build a new building… so maybe you simply got to zero resources, but since you couldn’t see it update in the UI, you didn’t realize?

      I hope this can help a little, feel free to send me another message if it doesn’t solve your problem! 😉

      1. Hello Again
        It helped with some of the problems so it now works with taking resources from the values and updating the text to match the values. but now it won’t turn off the buttons and I can’t figure out what is the problem.
        I’ve uploaded the project to my GitHub: https://github.com/Sakref00unholy/RTS-Builder-Game
        again thank you for the big help I’m learning a lot from it.

        1. Hello!
          I’m really happy the tutorial is of interested and that I can help with your issues 🙂

          Nice, thanks for sharing the Github repo! I’ve found the problem, it’s a little mistake in the name of the events: you are sending the event “CheckBuildingButtons” in the BuildingPlacer (with 2 “t”s) but receiving/listening to the event “CheckBuildingButttons” in the UIManager (with 3 “t”s).

          Simply use the same name everywhere and you will get the proper toggle on/off on the buttons 😉

          1. Hallelujah! thank you! xD
            now it works completely as it should. 😀
            I have spent around half of today looking with no luck xD
            so again a huge thank you 😀

Leave a Reply

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