My game’s Entity-Component-Event architecture

For programmers just looking to compare notes, here’s how I architected my game’s codebase.

It’s a simple, story-driven, 2-D adventure game, so the architecture requirements/trade-offs break down like this:

Object-oriented with nice specific classes for the entities (e.g. PlayerEntity) and component’s (e.g. PlayerPositionComponent) because I come from a professional programming background and wanted to have expressive abstractions for myself. I like old-fashioned modularity so there’s code in the components, and code in the entities, so right off the bat you know I willfully misunderstood the point of Entity-Component-System (ECS) design.

Event-driven. Instead of systems, I use an event bus, so for example, when you want to check collisions between the player and a table in the current room, the player sends out an event saying “Will I collide with you?”, and all subscribing objects, such as the table’s entity or one of its components, will inspect the player’s new position and mark the event with a collision if there is one.Why do it this way? I don’t have a rigorous answer, but I think I settled quickly on an event bus out of some frustrations trying to understand what robust code for ECS systems actually looks like. I must’ve read 10 ECS tutorials but still couldn’t figure out how it’d apply to my game. Entities are just lists of components, and components just hold data, so how do you sensibly organize all your logic in systems? Coming from my background in professional backend systems development, it just seemed easier (for me) to use my familiar OOP approaches. And one of those was using an event bus to decouple all my entities and components. In other words, event-driven design is my answer to how to write good systems classes.

JSON for configuration, because it’s easy to read and write, both for humans and code. Also, I knew it already. This has worked well so far, but I wonder where I’m going to hit the point where I wish I had fancier logic in my character/room/cutscene descriptions. I’m expecting (hoping) that the current state of affairs, where I specify what handler/logic class to construct via JSON string key-value pairs, is going to be good enough.

The theme throughout these decisions was how to get my game built quickly, by taking advantage of approaches I already knew. I’m just making this game for a lark. I’m not trying to become a proper, wise game developer. I just want to build and release something within a year.

So those are my trade-offs. Everything was judged by familiarity from my professional programming experience. I don’t claim to understand ECS (quite the opposite — I know I don’t), and I’m probably doing something wrong, but now you know.

Miscellany

My trick for pausing the game action, say when there’s a dialog bubble up on the screen, is to use a global EventPolicy interface that filters out events:

class DialogEventsOnlyPolicy : public EventPolicy {
  public:
  DialogEventsOnlyPolicy() {}

  std::unique_ptr<Event> handleEvent(std::unique_ptr<Event> e) override {

    // Allow rendering of all entities.
    // Yes, I use dynamic casting here. Shame on me. 
    if (dynamic_cast<RenderTimeEvent*>(e.get()) != nullptr) {
      return e;

    // Allow unpausing.
    } else if (dynamic_cast<UnpauseEvent*>(e.get()) != nullptr) {
      return e;

    // Don't allow any other kind of event.
    } else {
      return nullptr;
    }
  }
};

The hardest problems with this approach have been subtle bugs in event ordering, but having nice class names, plus the Visual Studio debugger, have made debugging much less painful than I bet it could’ve been.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s