Please Critique my Entity System

Published September 08, 2011
Advertisement
I code in a vacuum. I learned C++ from books and the 'Net, never took a class, and all my friends are humanities types. So I can't get any kind of RW peer review. That's where you come in.

I've taken a first pass at an entity/component system. I've neither written nor used such a system before, and while this code compiles and preliminary tests produce expected results, I suspect there may be pitfalls in my design. Also, my brain is a little fried at the moment.

The approach is that entities are integer ids, components only contain data, and that other systems (logic, input, collision, etc.) use and act on components relevant to their purview. I've liberally adopted code from and been inspired by numerous websites and -pages over the last couple days, and I'm too close and too frazzled to it to spot trouble spots. If you react with a WTF please let me know. Thank you.

ComponentTypes.hpp, incomplete--just a few to test out the system
[spoiler]
[source lang="cpp"]
#ifndef _COMPONENT_TYPES_HPP_
#define _COMPONENT_TYPES_HPP_

enum COMPONENT_TYPE
{
COMPONENT_CAMERA,
COMPONENT_MOVABLE,
COMPONENT_RENDERABLE,
COMPONENT_XFORM
};

#endif // _COMPONENT_TYPES_HPP_
[/source]
[/spoiler]

BaseComponent.hpp
[spoiler]
[source lang="cpp"]
#ifndef _BASE_COMPONENT_HPP_
#define _BASE_COMPONENT_HPP_

#include "ComponentTypes.hpp"
#include "../EntitySystem.hpp"

typedef unsigned int Entity;

/*********************************************
* BaseComponent
* This simply provides GetType()
* and a common base class, of course ;)
*/
class BaseComponent
{
public:
virtual COMPONENT_TYPE GetType() const = 0;
};

/**********************************************************
* AutoRegistrar< T >
* This auto-registers Create(), which creates a new T*,
* with the EntitySystem.
*/
template< class T >
struct AutoRegistrar
{
AutoRegistrar()
{ EntitySystem::Register( TplComponent< T >::GetTypeStatic(), AutoRegistrar< T >::Create ); }
static BaseComponent* Create() { return new T; }
};

/***********************************************
* TplComponent< T >
* Each component T derives from this class.
*/
template< class T >
class TplComponent : public BaseComponent
{
public:
TplComponent() { }
virtual COMPONENT_TYPE GetType() const { return _type; }
static COMPONENT_TYPE GetTypeStatic() { return _type; }

private:
static COMPONENT_TYPE _type;
static AutoRegistrar< T > _registrar;
};

template< class T > AutoRegistrar< T > TplComponent< T >::_registrar;

#endif //_BASE_COMPONENT_HPP_
[/source]
[/spoiler]

XformComponent.hpp, sample component
[spoiler]
[source lang="cpp"]
#ifndef _XFORM_COMPONENT_HPP_
#define _XFORM_COMPONENT_HPP_

#include "BaseComponent.hpp"
#include
#include

struct XformData
{
XMFLOAT4 _position;
XMFLOAT4 _scale;
XMFLOAT4 _rotation;
};

class XformComponent : public TplComponent< XformComponent >
{
public:
XformData& Data() const { return _data; }

private:
XformData _data;
};

COMPONENT_TYPE TplComponent< XformComponent >::_type = COMPONENT_XFORM;

#endif //_XFORM_COMPONENT_HPP_
[/source]
[/spoiler]

EntitySystem.hpp
[spoiler]
[source lang="cpp"]
#ifndef _ENTITY_SYSTEM_HPP_
#define _ENTITY_SYSTEM_HPP_

#include
#include

enum COMPONENT_TYPE;
typedef unsigned int Entity;

class BaseComponent;
class EntitySystem
{
public:
EntitySystem() { }
~EntitySystem();

/**************************************************
* Returns a new Entity with no attached components
* If there are any entites in the dead entities list
* it will reuse one of those.
*/
Entity CreateNewEntity();

/**************************************************
* Deletes all components attached to the Entity,
* then moves the entity from the live entities list
* to the dead entities list.
*/
void DestroyEntity( Entity );

/**************************************************
* Checks to ensure the entity does not already
* have a COMPONENT_TYPE component attached.
* Then, creates a new COMPONENT_TYPE component
* and attaches it to the Entity.
*/
void AttachComponent( Entity, COMPONENT_TYPE );

/**************************************************
* Populates the vector with all entities
* that have a component of COMPONENT_TYPE attached.
*/
void GetEntitiesByComponentType( COMPONENT_TYPE, std::vector< Entity >& );

/**************************************************
* Populates the vector with all components
* attached to the given Entity.
*/
void GetEntityComponents( Entity, std::vector< BaseComponent* >& );

/**************************************************
* Registers a component creation function and
* associates it with COMPONENT_TYPE.
*/
typedef BaseComponent* ( *ComponentCreator )();
static void Register( COMPONENT_TYPE, ComponentCreator );

private:
// Prevent copying and assignment
EntitySystem( const EntitySystem& );
EntitySystem& operator=( const EntitySystem& );

BaseComponent* CreateNewComponent( COMPONENT_TYPE );

/**************************************************
* I don't like these std::map typedef names, but
* they work, and I can't think of anything better.
*/
typedef std::vector< Entity > EntityVector;
typedef std::vector< BaseComponent* > BaseComponentPtrVector;
typedef std::map< Entity, BaseComponentPtrVector* > EntityComponentMap;
typedef std::map< COMPONENT_TYPE, EntityVector* > ComponentTypeEntityMap;
typedef std::map< COMPONENT_TYPE, ComponentCreator > CreateComponentMap;

EntityVector _entities;
EntityVector _dead_entities;
EntityComponentMap _entity_map;
ComponentTypeEntityMap _component_type_map;
CreateComponentMap _create_component_map;
};

#endif //_ENTITY_SYSTEM_HPP_
[/source]
[/spoiler]
Next Entry Square One
0 likes 4 comments

Comments

Ashaman73
I've got a similar system, here are some thoughts:

In my opinion performance and accessibility is more important than flexibility, because once in game development the number of different components will be stable really quickly, but accessing the components will be done always, all the time, even at runtime :P.

I would avoid making the type static and using a virtual function to access it. Instead put the COMPONENT_TYPE as member variable in the BaseComponent class and access it per inline getter. Setting would be done in the constructor or with some kind of init method when you need to use the default constructor. The advantage of it is faster access (no virtual method) by adding only a few bytes and, which is more important, you can use one class for several components. This is sometimes important when other parts of your code reacts depending on the component type.

I would add a real Entity class and declare your current Entity as EntityId. The Entity class would provide direct,cached access to your Components. Attaching/detaching components would be accomplished by the EntitySystem class only (making the attach/detach methods of the Entity class private and making the EntitySystem class a friend class).
September 09, 2011 12:49 PM
yckx
Thanks for your comments. Now that I've had some time away from the code, I need to take a second look at it with fresh eyes and your advice in mind.
September 10, 2011 04:15 AM
latent
I don't know that I have a lot to suggest in the critiquing - more just a little support. After working on something similar for my own project this last weekend I have a lot more respect for how convoluted it can all get.
September 12, 2011 01:17 AM
yckx
Yeah, I had some of it overly complicated for a bit. I couldn't figure out how to solve something (I forget now exactly what) and then realized if I just excised the troublesome bits, the problem went away and I still had the functionality I needed :D
September 13, 2011 04:46 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement