This past week, I have been focused on learning about entity component systems, or ECS. This is an interesting idea that brings in ideas from the relational database field. It started getting popular in the early 2000s with a talk at Game Developer Conference by Scott Bilas. As they were initially difficult for me to find, I have included a copy below. The blog that I really learned a lot from was Adam Martin’s five part series (especially post 5). The short short version is that instead of trying to code or encapsulate game objects as object-oriented classes, keep the logic and the data separate.
Now, this idea has not been foreign to me. When I started programming in BASIC, Pascal, Assembly, and C (those were my first languages), the procedural and modular programming approaches were the easiest to teach and learn. Object-Oriented Programming was interesting to me, but as a teen it was a little strange. Today, if I can think of a HAS-A relationship or an IS-A relationship, then I find it easier to decide whether to be more data-oriented or object-oriented. But procedural programming and having sub-modules process entire lists of structs kind of faded into the background when OOP was being touted as the best way to do anything. As a young person learning, you tend to hop on what seems industry standard and then go overboard before you learn to start balancing all the techniques together.
Still, the OOP influence on my game programming has had a long lasting impact. It’s the go to tool for starting a new project, but I have had difficulties wrapping my brain around why it feels clumsy to solve problems like game logic. So, having caught a couple of data-driven talks in the last year including CppCon14’s Data-Oriented Design by Mike Acton, I have been refreshing myself on other related techniques. It was a little disheartening then to see a talk from 2002 and compare the results from my own small games with the AAA games of today. But that’s not how I roll, my philosophy is that when you have failed and/or lost time, just try again and make it better the next time.
So what are entity component systems? The main idea is that you maintain a list of entities
. An entity is an integer which uniquely identifies it in the database. In database lingo, this is a primary key. The second list (or table) is the list of components
. Each component has a unique integer id, its official name, a description, and the name of the table that stores its information. An example of a component would be (1, “POSITION_COMPONENT”, “The position of an entity”, “positions”). The positions
table is going to contain a primary key, x, y, and z coordinates. You could think of this as an array of a fixed arrays with 3 components.
Now we need to connect the component, the entity, and the row together. So we add another table entityComponentData
with three columns of information: entity id, component id, and the id of the row containing the data. Now this is beyond the scope of this blog post right now, but you would use the power of the INNER JOIN to connect all the tables together.
In an object-oriented language like C++, we can make this whole thing a little simpler. We could define a struct
for the position data. The components and entities can use a variety of data structures. For instance, we can use a std::unordered_map
— which is a hash table — to store the list of components and then a nested unordered map to store the link from the entities to the components. Another approach is to maintain a set of arrays that we resize. It’s more of an approach of managing this data. Consider the following C++ snippet which shows how you would retrieve the data for a particular component.
struct Component {};
class EntityComponentSystem {
unordered_sat<int> entities;
unordered_map<int, unordered_map<int, Component*>> ecs;
Component* getEntityComponent(int entityID, int componentID) {
if (!ecs.count(componentID)) return nullptr;
if (!entities.count(entityID)) return nullptr;
return ecs[componentID][entityID];
}
};
I had some success using a static member variable which stores the componentID and then use a template function which automatically casts to the right component subclass.
enum class ComponentType {
Derived;
};
struct DerivedComponent : public Component {
static const ComponentType ctype;
};
ComponentType DerivedComponent::ctype = ComponentType::Derived;
class EntityComponentSystems {
template <typename T>
T* getEntityComponent(int entityID) {
if (!components.count(T::ctype)) return nullptr;
if (!entities.count(entityID)) return nullptr;
return (T*)entityComponents[T::ctype][entityID];
}
};
With this method, you can write statically typed code that ensures your entity component system is returning the right class. I did not use smart pointers here for sake of brevity, but you could totally use something like that to make the memory management a little more automatic.
Until next time, I will be playing around with the idea of an ECS and try to think of other things that I could use it alongside with.