Tuesday 19 November 2013

Concerning component-entity systems


Recently I have been discussing component based design with a friend, and he asked me how my implementation worked out in my platformer. My description grew quite long, so I thought I would make a post out of it - I wanted to do it anyway, I was just always procrastinating. And since there is not much practical info available about it, hopefully it might be useful for others as well. My system is based on this article, and probably this post will not be too clean without reading that one first.

In my C++ implementation Entity is just a typedef to int, they can be created by entities_create(); and recycled by entities_destroy(Entity i);. Currently there are 6 components: Common, Physics, Graphics, Animation, Audio, Camera.

Components


The most basic is the Common component, it contains the position, rotation and facing of an entity. I wanted to group together all the data into one component that most entities have and most other components access.

The Graphics component assigns images to entities. It accesses the Common component to know where to draw the image. The Animation component is quite similar, but instead of static images it uses animation instances.

The Audio component is also similar to the Graphics, it maps entities to Sound instances. At first I thought that it might be a limitation that one entity can have only one sound playing, but then I realized that I can create a new entity for each sound I want to play if needed. Simple.

The Physics part is more complicated, it consists of a main component which maps entities to Box2D bodies, and a subcomponent which maps entities to foot sensors (these are used to query whether an entity is standing on something). These are separate components, but due to the underlying Box2D implementation they are intertwined (the foot sensor needs to access the body), so I just put them together. A body can be either static (for platforms) or dynamic (for moving objects), so it could be possible to handle them as two separate components. Each turn the Physics component updates the position and rotation attributes in the Common component.

The Camera component is a bit special: only one entity can have it (that is, the Camera component has an Entity target; variable instead of a mapping from entities). I don’t remember the original article mentioning such components, but I thought it was logical to do it this way. Essentially it uses OpenGL transformations to follow The Entity so that it appears in the centre of the screen. Changing it to follow other entities can be used to simply implement nice effects, e.g. following a projectile and zooming in on it.

These are the components on the C++ side, but it’s just half of the story; most of the functions of the components are exported to the embedded Lua interpreter, so other components can be implemented in scripts. The C++ components are quite general and could be used to implement many kinds of games (at least I believe so), while the components related to the actual gameplay can be done in Lua. So far I have only implemented the Bullet component, but I was planning others (e.g. AI). Maybe one day I will continue working on this project.

Technical details


For each component I have header file to make all the public functions available to other components (and, more importantly, to Lua), for example common.h contains:

void common_init();
void common_add(Entity e, float x=0, float y=0, float angle=0);
void common_delete(Entity e);

And <name_of_component>.cpp contains the implementation. Private variables and functions are static, public functions are exported in the header, and public variables are global. Yes, global, the second most evil thing after goto, and just slightly better than premature optimization. Please don’t stone me… at least until I blow this whistle. Even if I do say "global". But to be serious, I think this was one of the biggest advantages of using a component-entity system: that it taught me how to tame my globals. I was always told not to use them, because who knows which part of the program modifies them at what time, so they can cause lot’s of headache. But with this system it’s quite clear which component uses what other components, so the modifications can be easily tracked (at least I can do it since I am the only developer… this might be different in a team). And anyway, passing around one huge object containing all the game state is no better.

To provide the mappings between entities and components I use two classes (T is the type of the component, usually a struct):

template <class T> class DenseComp {...};
template <class T> class SparseComp {...};

DenseComp uses std::vector to store the component, and it is used in the Common component. It works since a mapping from Entities (which are in fact integers) to component data is needed, and that’s what a vector does. Since usually most of the entities have the Common component, it’s not wasting space, and this way accessing the Common component of an entity (which is quite frequent) happens in constant time.

SparseComp uses std::map to map entities to a component, it is used in general for other components. It is more space-efficient than a vector when just a part of the entities have the component, and usually querying the component of a specific entity is not too frequent, so the slower lookup is not a problem.

Creating two separate classes for components might have been premature optimization, since currently I just have a handful of entities, so it does not really matter. Maybe using just the one with std::map would have been enough.

Using plain C functions and structs instead of C++ classes to implement components has a big advantage: it’s much easier to bind them to scripting languages. Most scripting language implementations support binding of C functions, but binding methods of C++ classes is complicated at best and usually requires some binding generator. And since Entities are just integers, it’s straightforward to use them in scripts, no need for wrapping classes or handling them as special user-data.

On a sidenote, I was experimenting with turning the engine inside-out, that is, throwing out the Lua interpreter, compiling the code as a shared library and then loading it from Ruby or Python or LuaJIT or any other language implementation which supports it, and this way making it available for other scripting languages. It was working fine on Linux, importing shared libs is a piece of cake with LibFFI in Ruby and ctypes in Python, but unfortunately I had issues on windows: loading the dll hung. The problem was related to using the SFML library, and I could not solve it even after a few days of debugging, so I gave up. I considered migrating to SDL, but it would have been much of a hassle.

Summary


All in all the component-entity system worked out really well in my engine. My codebase is well organized, related code is contained in small, well-separated units. Adding a new feature usually means creating a new .cpp and .h file and requires very little change in existing code. Extending functionality is also easy, since modifications usually affect just one component.

This design also hands itself well for building a base engine with general functionality and creating the specific gameplay in scripts. I have no experience with building a complex engine based on OOP, I can imagine that it would work just as well.

One thing that is not clear is when to group together simple components into more complex ones, or the opposite, when should it be advantageous to cut a complicated component into several smaller ones. I suppose it needs some experience to decide this.

I did not completely throw objects out the window: I still use them as basic building blocks, and they are great for that. But on higher levels there are just entities and components. I think it’s like playing with lego. When you build a house you use the small bricks, but don’t really design it on the level of bricks; you say that the house should have a kitchen, a bedroom, and a garage, and then design those with bricks, and decide how they could fit together to make up a house.