This is not a specific question, but rather someone who is looking for opinions about a typical design problem. In a component based data-driven games, I've often seen something similar to the following when accessing attributes:
int* health = mOwner->get< int >( HEALTH_ATTRIBUTE );
if (health)
*health -= 10;
Or, alternatively:
if (mOwner->hasAttribute( HEALTH_ATTRIBUTE ))
{
int health = mOwner->get< int >( HEALTH_ATTRIBUTE );
mOwner->set( HEALTH_ATTRIBUTE, health - 10 );
}
Where mOwner
is a pointer to the parent game object. In this example, assume that the game object stores a map of attributes which are downcasted to the correct type at runtime.
There are some problems with this approach. Mainly, it is prone to bugs since it requires the programmer to know the the type of the attribute they are after. If I was working on a small team or by myself, this wouldn't be an issue. However for my requirements, this is undesired.
The only way to have compile-time checks is to create a helper function for each attribute that returns the proper type, so our code would then look like:
int* health = Attributes::getHealth( mOwner );
Where Attributes
is declared as a namespace. Because the type is known at compile time, this gives us several advantages. In addition to the compile-time check, we gain the flexibility to easily declare certain attributes as constant, or that can't be modified outside the game object, etc. Of course, the downside is that it becomes necessary to manually create a new function for each and every attribute in the game.
Thoughts? Opinions?
EDIT:
After thinking it through a little more, I realized there is a way to define the function once using templates to avoid code duplication. For example:
template <typename Type, int ID>
class Attribute
{
public:
typedef Type type;
static int getID()
{
return ID;
}
static type getData( const GameObject* obj )
{
return obj->getAttribute < type >( ID );
}
};
And then for each attribute we have in our game,
typedef Attribute< std::string, MODEL_NAME > ModelName;
typedef Attribute< Vector3, POSITION > Position;
typedef Attribute< int, HEALTH > Health;
/// ... and so on ...
And when we want to access an attribute:
int health = Health::getData( mOwner );
Or alternatively, if we want to:
Health::type health = Health::getData( mOwner );
If we don't know the health attribute exists or not:
int* health = Health::getData( mOwner );
if (health)
// ....
I think this approach gives a lot of flexibility while still maintaining type safety.