I've been getting interested in the Entity/Component style of game programming, and I've come up with a design in C++ which I'd like a critique of.
I decided to go with a fairly pure Entity system, where entities are simply an ID number. Components are stored in a series of vectors - one for each Component type.
However, I didn't want to have to add boilerplate code for every new Component type I added to the game. Nor did I want to use macros to do this, which frankly scare me. So I've come up with a system based on templates and type hinting. But there are some potential issues I'd like to check before I spend ages writing this (I'm a slow coder!)
All Components derive from a
Component base class. This base class has a protected constructor, that takes a string parameter. When you write a new derived Component class, you must initialise the base with the name of your new class in a string. When you first instantiate a new
DerivedComponent, it adds the string to a static hashmap inside Component mapped to a unique integer id. When you subsequently instantiate more Components of the same type, no action is taken. The result (I think) should be a static hashmap with the name of each class derived from Component that you instantiate at least once, mapped to a unique id, which can by obtained with the static method
Component::getTypeId ("DerivedComponent"). Phew.
The next important part is
TypedComponentList<typename PropertyType>. This is basically just a wrapper to an
std::vector<typename PropertyType> with some useful methods. It also contains a hashmap of entity ID numbers to slots in the array so we can find Components by their entity owner. Crucially
TypedComponentList<> is derived from the non-template class
This allows me to maintain a list of pointers to
ComponentList in my main ComponentManager, which actually point to
TypedComponentLists with different template parameters (sneaky).
The Component manager has template functions such as:
template <typename ComponentType> void addProperty (ComponentType& component, int componentTypeId, int entityId)
template <typename ComponentType> TypedComponentList<ComponentType>* getComponentList (int componentTypeId)
which deal with casting from ComponentList to the correct TypedComponentList for you.
So to get a list of a particular type of Component you call:
TypedComponentList<MyComponent>* list = componentManager.getComponentList<MyComponent> (Component::getTypeId("MyComponent"));
Which I'll admit looks pretty ugly.
Bad points of the design:
- If a user of the code writes a new Component class but supplies the wrong string to the base constructor, the whole system will fail.
- Each time a new Component is instantiated, we must check a hashed string to see if that component type has bee instantiated before.
- Will probably generate a lot of assembly because of the extensive use of templates. I don't know how well the compiler will be able to minimise this.
- You could consider the whole system a bit complex - perhaps premature optimisation? But I want to use this code again and again, so I want it to be performant.
Good points of the design:
- Components are stored in typed vectors but they can also be found by using their entity owner id as a hash. This means we can iterate them fast, and minimise cache misses, but also skip straight to the component we need if necessary.
- We can freely add Components of different types to the system without having to add and manage new Component vectors by hand.
What do you think? Do the good points outweigh the bad?