Nomad Game Engine: Part 5 — Systems

Finally some game functionality

Niko Savas
Jun 30, 2017 · 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.

After a bunch of posts on theory, we’re finally ready to touch upon the part of the engine where the actual gameplay code goes. If any of the terms in this post are confusing, check out these two posts for a good overall picture of the engine architecture:

Our goal this post will be to make a very simple system: the Movement System. This system relies on two components:

The goal of the movement system will be to update the position and velocity of all entities that have both a Movement component and a Transform component. In pseudocode, this would look something like:

for (all entities with both Movement and Transform components) {
  entity.movement.position += velocity * dt;
  entity.movement.velocity += acceleration * dt;
}
Movement System

Before we get any further into implementation I want to make a couple points:

  1. This specific system might not actually be set up this way — in a more complex 3d game with things like scene graphs, the “Movement System” might not be a system at all. I’ll cover the idea of “custom functionality” in a game engine in a future post, but for now just understand that this might not be the “best” way to handle movement in a game (although it does work pretty well for simple games)
  2. There are actually some really cool optimizations we can do to speed systems like this up, but since we’re using it as a demo for now I’ll cover those optimizations in a future post.

Cool, now let’s jump into it. Here’s the System class:

There are two main sets of functionality at play here, so I’ll cover both of them before jumping into the implementation.

Entity registration

As mentioned in previous posts, a system has to specify which components it cares about. In this case, our MovementSystem wants to pay attention to any entities that have both a Transform component and a Movement component. To keep track of which components a system cares about, we have a std::bitset<32> systemSignature. This signature represents all of the components that are important to our system.

There are two different methods I've seen to deal with entity registration and I'll try to detail both here.

Method 1

The first method is to have each system specify which components it cares about at runtime. This is the solution employed by EntityX. Essentially you make a call to the World and ask for all components that fit a given criteria. In this example, that would look something like this:

The benefit of this is that systems don't need to hold much state by default - each system can make calls for whatever combination of components anytime and get the correct entities. In addition, there is no extra overhead to adding or removing components - your information is coming directly from the component managers. The biggest downside is that this lookup needs to be done every time to check for changes. This can be significantly sped up through caching, but ultimately a method is still called and a lookup is made every system, every update.

Method 2

The second method (and the one that I like better) is to have the World pay attention to when components are added and removed from an entity, and "register" or "deregister" entities from the necessary systems. Here's a quick example:

Around the black box you can see which entities are registered and deregistered from each system.

The advantage of this method is that every system update is as easy as iterating over a list and getting the required components. We already know which entities we care about so no lookup needs to be done. The main disadvantage is that overhead is added whenever a component is added or removed. This really decreases performance if you're adding or removing lots of components at once, because every entity would need to be checked across all systems. Luckily since we're using the bitset it's just a comparison, but it still could be difficult if lots of removals/additions happen on the same update.

Both of these solutions will act very similarly in most situations, but the reason I prefer the second one is that I think most entities tend to keep most of their components most of the time - which is to say that it's relatively uncommon for components to be added or removed, and much more common for them to remain the same. To work with this second solution, we have these two methods on our System:

void registerEntity(Entity * entity);
void deRegisterEntity(Entity * entity);

The assumption is that these will be called by the world whenever our system needs to change its list of registeredEntities.

Execution Logic

The second important functionality to understand is how the constructor, init(), update(), and render() interact.

The Constructor

This should be defined specifically for each unique system. Any dependencies the system might need should go here. For example, let's say we had a system that needed a database connection - we could pass this in the constructor:

In addition to satisfying any dependencies, systems should also set which components they care about by modifying the systemSignature - if this is confusing it will make more sense when we get to implementation. The assumption here is that after this happens, everything is automated and systems can function independently.

init()

The init method is expected to be called *before* update() or render() are run, but *after* the basic game initialization has happened (all systems have been added, all components have been added, etc.). There are a couple uses for this init method, but ideally most of the heavy lifting that has to happen will happen in this init method.

update() and render()

These two both start getting called *after* init() gets run. If you're confused by the difference between update and render, I'd highly recommend checking out this post.


Implementation

Now let's look at implementation for our specific situation.

Constructor

Our MovementSystemdoesn't have any external dependencies, but we do still need to set our signature in the constructor. Obviously in this example I have hard-coded 32 components, but in reality you'd want to be able to know how many components are in your game (or set the size to a large "maximum" number). This bitset also assumes that each component type has its own unique id (counting up from 0). I'll cover how this can be automated in a future post, but for now we will simplify it by assuming that we've decided the following mapping:

Transform Component: 3
Motion Component: 18

In our constructor, then, we'd want something to the effect of:

As I mentioned, in a more complex engine we'd have a more intuitive way of mapping between id and component types.

init

We don’t actually have any special initialization for the movement system.

update

Our update method is where we are running the main functionality. Here’s what it looks like:

Don’t worry about the world->unpack method, we will cover that in the next blog post. For now just understand that it does the lookup for the components.

render

Once again, nothing needs to be rendered by this system.

And we’re done! Our final system looks like this:

Other system examples

This is a relatively simple example meant to illustrate the basic concept of a system. I will make a separate blog post that gives an example of a more complex system that uses all of the methods in a system (notably render() and init() which aren’t used at all in this example).

Current Progress

Good news is that using dear imgui, I’ve managed to set up a much better debug interface for my engine. Its current functionality includes:

  • Creating/Removing Entities
  • Adding/Removing components from entities
  • Modifying a component

This was the last big milestone for getting the basic functionality working. Moving forward I plan on cleaning up the code for what already exists, and start learning OpenGL to provide even better debugging tools for myself. I’m also currently mulling over the idea of adding lua scripting capabilities to the engine, but I’m not sure if I’ll end up pursuing that.