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 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, most 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. At that point, you don’t have a network of individual organisms but one big organism with a somewhat Frankenstein body. In OOP, this can happen particularly quickly: think of how easy it was 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/initialises it and then returns it. This is a way to have a unique version of an object that is initialised just once and then accessed as many times as necessary.

Note: this pattern is like many others 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 our 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 customisable 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

With that new mechanic in our toolbox, next week we’ll get back on track and implement another crucial feature in any RTS game: the ability to select units by dragging a box around them or clicking directly on one.

11 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 😀

  2. Hey there, me again =)

    I’m struggling a bit with this event system…
    I created a CustomEventData.cs with the following input:

    public class CustomEventData
    {
    public BuildingData buildingData;

    public CustomEventData(BuildingData buildingData)
    {
    this.buildingData = buildingData;
    }
    }

    [System.Serializable]
    public class CustomEvent : UnityEvent {}

    But Unity told me that
    Assets\Scripts\CustomEventData.cs(13,28): error CS0246: The type or namespace name ‘UnityEvent’ could not be found (are you missing a using directive or an assembly reference?

    And I don’t get it… :-/

    1. Hi! Hum… are you sure you did import the UnityEngine.Events package ? That’s what the line at the top of the 1st script of the tutorial is for: using UnityEngine.Events;, and it’s that package that contains the UnityEvent class 🙂

      Generally speaking, when you have an error with something like “are you missing an assembly or reference?”, it means that you forgot an import, or you’re using a class out of your namespace, etc.

      Hope it helps!

      1. Hi,
        no, I did not import anything. I just created a new .cs file and wrote down both classes into one file.
        Do you have those classes in another file, because I didn’t found anything like a CustomEventData.cs file in your git.
        This chapter here was somehow confusing me =)

        1. Hey,
          that’s because this class is removed and replaced by something else later on in the tutorial 😉

          Working with events can be a bit weird at first, so if you’re not familiar with those I really recommend you take a look at Unity’s tutorial on this topic: there’s a ~1h video tutorial that I think gives a good sum up of the core ideas:

          https://learn.unity.com/tutorial/create-a-simple-messaging-system-with-events#5cf5960fedbc2a281acd21fa

          And my EventManager is directly adapted from their code, so it will probably help you “transition” back to this article afterwards 🙂

  3. Heya Mina,

    I found this fabulous tutorial series recently and been amazed by the quality of contents I was able to find here.

    If you allow me to just suggest something kindly, I would recommend changing the name of your event in your example to replace the UIManager method calls in the BuildingPlacer class. Not that “UpdateResourceTexts” and “CheckBuildingButtons” aren’t explicit names, but I think they’re a bit inappropriate regarding the context. I think we agree on the fact that an event is a “what’s happening” thing. Here, the “what” is maybe more “Place building” than “Update resource”.
    Picking an event name closer to the definition of the context that is triggering the different calls is more explicit than picking names closer to what you want to trigger on the class with functions listening to this event. Furthermore, pick your event name like this increase the readability of your code as the reviewer will be able to understand that one function is triggered by a placed building event and so will be able to found out the class/function a building is placed more easily as you couldn’t be clearer.

    So to resume my thoughts, here is what I was thinking of:

    – On BuildingPlacer:
    EventManager.TriggerEvent(“OnPlacedBuilding”);

    – On UIManager:
    EventManager.AddListener(“OnPlacedBuilding”, _HandlePlacedBuilding);

    private void _HandlePlacedBuilding()
    {
    _UpdateResourceTexts();
    _CheckBuildingButtons();
    }

    Ofc it’s my own POV, and you’re totally free to do it the way you want to! Just wanted to share my reflection with you. 🙂

    1. Hello, and thank you for your really nice+interesting comment 🙂
      I’m glad you like the tutorial – and your suggestion is a very good one!

      It is very true that an event should describe what’s going on, rather than what will happen in reaction – and to be completely honest, I’ve been bugged by these event names for quite a long time… but at that point in the series (41 episodes out, still going ^^), I haven’t yet taken the time to correct them 😀
      But now that you point it out… it’s really something that I should take care of!

      I do have the next tutorials planned, but I think I may insert one “interlude” somewhere to fix this – you’ve comforted me in thinking that this is an issue that needs to be dealt with!

      Feel free to share any other idea of improvement/fix you have,
      it’s always great to interact with the readers and get good advice from you all!

      Cheers 🙂

Leave a Reply

Your email address will not be published.