(What I'm describing is based on this design: What is an entity system framework?, scroll down and you'll find it)
I'm having some problems creating an entity-component system in C++. I have my Component class:
class Component { /* ... */ };
Which is actually an interface, for other components to be created. So, to create a custom component, I just implement the interface and add the data that will be used in-game:
class SampleComponent : public Component { int foo, float bar ... };
These components are stored inside an Entity class, which gives each instance of Entity a unique ID:
class Entity {
int ID;
std::unordered_map components;
string getName();
/* ... */
};
Components are added to the entity by hashing the component's name (this probably isn't such a great idea). When I add a custom component, it is stored as a Component type (base class).
Now, on the other hand, I have a System interface, which uses a Node interface inside. The Node class is used to store some of a single entity's components (as the System isn't interested in using all of the entity's components). When the System has to update()
, it only need to iterate through the Nodes it stored created from different entities. So:
/* System and Node implementations: (not the interfaces!) */
class SampleSystem : public System {
std::list nodes; //uses SampleNode, not Node
void update();
/* ... */
};
class SampleNode : public Node {
/* Here I define which components SampleNode (and SampleSystem) "needs" */
SampleComponent* sc;
PhysicsComponent* pc;
/* ... more components could go here */
};
Now the problem: let's say I build the SampleNodes by passing an entity to the SampleSystem. The SampleNode then "checks" if the entity has the required components to be used by the SampleSystem. The problem appears when I need access the desired component inside the Entity: the component is stored in a Component
(base class) collection, so I can't access the component and copy it over to the new node. I've temporarily solved the problem by casting the Component
down to a derived type, but I wanted to know if there is a better way of doing this. I understand if this would mean re-designing what I already have. Thanks.
Answer
If you are going to be storing the Component
s in a collection all together then you must use a common base class as the type stored in the collection, and thus you must cast to the correct type when you try to access the Component
s in the collection. The problems of trying to cast to the wrong derived class can be eliminated by clever use of templates and the typeid
function, however:
With a map declared like so:
std::unordered_map components;
an addComponent function like:
components[&typeid(*component)] = component;
and a getComponent:
template
T* getComponent()
{
if(components.count(&typeid(T)) != 0)
{
return static_cast(components[&typeid(T)]);
}
else
{
return NullComponent;
}
}
You will not get a miscast. This is because typeid
will return a pointer to the type info of the runtime type (the most derived type) of the component. Since the component is stored with that type info as it's key, the cast can not possibly cause issues because of mismatched types. You also get compile time type checking on the template type as it has to be a type derived from Component or the static_cast
will have mismatched types with the unordered_map
.
You do not need to store the components of different types in common collection, though. If you abandon the idea of an Entity
containing Component
s, and instead have each Component
store an Entity
(in reality, it will probably be just an integer ID), then you can store each derived component type in its own collection of the derived type instead of as the common base type, and find the Component
s "belonging to" an Entity
through that ID.
This second implementation is a bit more unintuitive to think about than the first, but it could probably be hidden as implementation details behind an interface so the users of the system don't need to care. I won't comment on which is better as I have not really used the second, but I don't see using static_cast as a problem with as strong a guarantee on types as the first implementation provides. Note that it requires RTTI which may or may not be an issue depending on platform and/or philosophical convictions.
No comments:
Post a Comment