Enceladus Project Dev Blog 2: Game Event Message Bus
Tags: enceladusproject, unity3d, gamedev
Published: 2020-04-25 23:15:35

#Intro
Very early on in the design of this project, I knew that I wanted to allow different game objects and entities to communicate with each other in a clean, decoupled manner. I haven't worked on many Unity3D projects, but the ones I have worked on quickly devolved into spaghetti; a bunch of objects calling other objects explicitly and being dependent on each other. This worked for smaller scale projects, but it quickly became a burden as the scope of the project increased. That's why I created this simple Message Bus class

{% gist https://gist.github.com/CRodriguez25/1ddafe2dbd889b636b10d2196554b6eb %}

There's not much to this class, but it has been a game changer for the development of this project. In this post, I'll go over the implementation, and give some examples of how it is used in __The Enceladus Project__.


##Usage
Let's start with the usage of this message bus, as this class can really be used without knowing how it works. This class has two public methods, `Subscribe<T>` and `Emit<T>`. The types (`T`) that you're subscribing to or emitting can truly be any type, though they're meant to represent a particular `Event` in the game that other objects may be interested in. One example object from __The Enceladus Project__ is the `CompleteLandingEvent`

```
public class CompleteLandingEvent
{
    public GameObject LandingPad { get; set; }
    public GameObject Ship { get; set; }
}
```

This event is meant to be emitted when a ship successfully lands at a `Landing Pad`. This is an event that potentially many parts of the game might care about, and so it was a good fit for an Event in our system. Here is how this event is actually emitted in the game. 

```
GameEventMessageBus.Emit(new CompleteLandingEvent {
    Ship = gameObject,
    LandingPad = _landingPad
});
```

And just like that, we've let the game know that the ship `Ship` has succesfully landed at landing pad `LandingPad`. Of course, if there's no one who `Subscribe`d to this event, then nothing will happen. So let's add someone who might care.

```
    // LandingPad.cs
    public void Start()
    {
        GameEventMessageBus
            .Subscribe<CompleteLandingEvent>(msg => ShipLanded(msg.Ship, msg.LandingPad));
    }

    private void ShipLanded(GameObject ship, GameObject landingPad)
    {
        if (landingPad != gameObject) 
            return;

        Animator.SetBool("OpenDoor", true);
    }
```

The above script is attached to landing pads. They care about the `CompleteLandingEvent` because, when a ship lands at a given landing pad, that landing pad needs to play a “open door” animation.

In the Start method, we let the message bus know that we care about that event by calling the `Subscribe` method. We specify the type of event we’re subscribing to, and we specify the action that should happen when the event is received (play the appropriate animation)

Now, when the "CompleteLandingEvent" is fired, our station doors should open. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/va6stgxvznpn5hp7j2qu.gif)

The beauty of this approach is that the code that fires the `CompleteLandingEvent` in the first place doesn't know anything at all about the door animations. It just lets "anyone who is interested" know. I can add a second interested party afterwards (say, to open up the Station UI screen) without having to touch any of the existing code. And like I always say, code that doesn't change doesn't break. 


##Implementation

Here's the code again. 
{% gist https://gist.github.com/CRodriguez25/1ddafe2dbd889b636b10d2196554b6eb %}

At the top, we instantiate our Dictionary of subscribers. It maps different C# types (our event types like the `CompleteLandingEvent`) to a list of Actions that need to happen when that event is fired. 

`    private static Dictionary<Type, List<Action<object>>> _subscriberDict = new Dictionary<Type, List<Action<object>>>();
`

When a caller calls the `Subscribe` method, it'll execute this line

`        _subscriberDict[type].Add(new Action<object>(o => handler((T)o)));
`

Which adds a new Action to our List of Actions that must be performed when this event is called. There's some wrapping and casting happening here, since our lists are `List<object>` but our subscribers are expecting the specific type they're subscribed to, but in the end the action will call the `handler` with the object of the appropriate type. 

Now, when someone calls the `Emit` method...

```
    public static void Emit<T>(T message)
    {
        var type = typeof(T);
        InitializeTypeKey(type);
        foreach(var handler in _subscriberDict[type])
            handler.Invoke(message);
    }
```

we iterate over each handler for that type, and just invoke that handler with the correct message. 


##Conclusion

This message bus is very simple, and still needs a little work (it'd be nice to have an unsubscribe method as well, to clean up any destroyed objects. I haven't needed it yet but I'm sure I will soon), but this is a great base that has led this project so far to have a neat, decoupled architecture.