Nomad Game Engine: Part 7— The Event System

Inter-System communication

Niko Savas
Aug 2, 2018 · 6 min read

This post is part of a series where I’m documenting my experience building an ECS game engine from scratch. Check out the homepage for this project for more posts, information, and source code.

Up until this point, we’ve almost got a working game engine — we have entities with components and systems acting on those components. This blog post will outline one of the last significant parts of a functional ECS — the event system.

Our space invaders game (assets: Kenney)

To illustrate the need for an event system, we’re going to construct a super simple game based on space invaders. Here are some quick requirements:

  • Spaceship can move side to side using the arrow keys
  • Spacebar can be pressed to shoot a laser
  • When a laser hits an alien, it disappears

If we build out this game similar to how we’ve done previous examples, we’ll end up with something like this:

I may write a future blog post which actually implements this entire game, but for now we’re just going to deal with these components and systems conceptually. If we walk through our requirements and write pseudocode for each, we can very quickly see a hole in what this blog series has covered so far.

Let’s take a look at the first requirement:

This requirement isn’t too bad — this is what we have the Player system for.

On to our second requirement:

Once again, this will go in our Player system:

None of this so far should be much of a surprise, we’ve talked about all these concepts in the previous blog posts. Now we get to the important requirement:

The previous two requirements had functionality that nicely fit into an existing system we had built explicitly for this purpose (the player system). A simple approach to implementing this 3rd requirement might be something like this:

The issue with this implementation is that we’ve now coupled two parts of our game that really have nothing to with each other: Physics (handling collisions), and Combat (gameplay of enemies and players fighting). This might not seem like a problem on the surface, but let’s imagine we end up making our game a bit more complex:

Now our code would look more like this:

Let’s remember that all this code is still in the Physics system. As our game grows more complex, we end up with more and more ridiculous code shoved into our physics system that has nothing to do with the physics it’s supposed to be handling. Clearly, we need a way for systems to communicate with eachother.

Events

Super simplified pub/sub. For more information, google is your friend

Enter events. Events in Nomad use a pub/sub design pattern. Systems can subscribe to events they care about as well as publish events that are relevant to them.

To decouple our Physics from our Combat, we will move the combat logic into it’s own CombatSystem, and allow the PhysicsSystemto publish a CollisionEvent that the CombatSystem will be subscribed to and handle. The implementation of this architecture mostly revolves around the EventBus. Our end goal is to let events be published in one line:

eventBus.publish(new CollisionEvent());

in addition letting subscribers subscribe in one line and use a member function as a handler:

eventBus.subscribe(this, &CombatSystem::onCollisionEvent);

My implementation for the event system is mostly based on this forum post. I spent quite a while experimenting with different implementations but this one ended up being the best mix of performance, readability, and reusability that I could find. All event routing is handled by the event bus, which holds on to a list of methods to call for each event, and calls them all when that event is received.

The primary challenge with this design is safely wrapping member functions so that they can be called at a later time. Enter MemberFunctionHandler :

While it might seem complex, really all it’s doing is holding on to a reference to the class instance and the offset of the method to be called. What’s nice about this implementation is that it uses static_cast<>, which adds a layer of safety and helps with performance vs dynamic_cast<>.

Now we just need our EventBus class, which holds on to a bunch of MemberFunctionHandlers and calls them when an event is published.

As long as each system holds on to a reference to a shared EventBus instance, published events will be safely and quickly sent to all subscribers.

Fixing what was broken

Going back to our original requirements, we first make an event:

And a new Combat system:

Now instead of having our collision resolution code in physics, we simply fire an event:

Notice how from the physics system’s perspective, we only care about collisions, not dealing with their side effects. This is good design because we’re not coupling our behaviours.

If we wanted to change how combat works (e.g. by adding shields, damage mitigation, etc.) we could now handle all of it in the Combat system as opposed to shoehorning it into the physics system. If we want to be really fancy, we can use a similar approach with the inputs, letting us further abstract away the library we use for input from the actual player movement code.

Further benefits: Achievements

In addition to the obvious communication between gameplay systems, we can actually leverage events for a whole bunch of useful things. For example, let’s say we decide to add achievements to our game, and have an achievement reward for dealing 100,000 damage.

Because we’re using events, we would have a DamageEvent being published every time the player deals or takes damage. All we’d need to do is have a new AchievementSystem subscribe to DamageEvent, and add to a running total when the damage event is from the player. In this way, our code is completely separate from all other game logic and we’re not going to have any bugs due to the achievement system existing or not. This is one example of the many benefits that having an event driven game engine can bring.

Even further benefits: Concurrency

While I haven’t talked about concurrency at all, having a game engine that uses message passing at its core gives many benefits. I’m hopefully going to cover concurrency in the future, but for now check out the Actor Model which actually bears a lot of similarities to the way we’ve been conceptually thinking about our game/engine. In theory, we could have different systems running on completely different threads, communicating and giving the game life solely through events.

Niko Savas

Written by

http://savas.ca/ — niko@savas.ca