Jump to content
  • Advertisement
Sign in to follow this  
jaeg

Basic Component Based Entity

This topic is 2398 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

So I've been working on a simple component based entity system so I can create entities on the fly without have to have extreme levels of inheritance. This way if I want an entity to be the player all I have to do is add the appropriate components to let it operate under keyboard control. Another reason I want to try this system out so I can have my AI be able to target anything that has a "targetable component". This way they can destroy props in their way to get to the player.
This is the basic system so far:
CEntity class


class CEntity
{
public:
CEntity();
virtual ~CEntity();

void addComponent(CComponent*);

CComponent* getComponentbyName(string);

void update();
protected:
private:
vector<CComponent*> components;
};



CEntity::CEntity()
{
//ctor
}

CEntity::~CEntity()
{
//dtor
}

void CEntity::addComponent(CComponent* newComponent)
{
components.push_back(newComponent);
}

CComponent* CEntity::getComponentbyName(string name)
{
for (unsigned int i=0;i<components.size();i++)
{
if (name.compare(components->name)==0)
{
return components;
}
}
return 0;
}

void CEntity::update()
{
for (unsigned int i=0;i<components.size();i++)
{
components->update();
}
}



CComponent:


class CComponent
{
public:
string name;

CComponent();
virtual ~CComponent();
virtual void update()=0;

protected:
private:
};



It's not game specific right now since I was just cranking it out last night to see if it even work. The entity will also contain an Irrlicht IAnimatedMeshScene node as well.
I have a factory designed that'll let me create entities based on a name you provide where it'll then proceed to load an external script informing it what components the entity will need. There is also a function that'll create it based on it's node. This is mostly so I can use my level editor which lets me give nodes names. The function takes the node and extracts the name and precedes like the former function.

Is this idea valid or should I be looking at a different way to approach this? The code is really simple right now because I just wanted to get the basic mechanism working before I integrate it into my game engine.

Share this post


Link to post
Share on other sites
Advertisement
This is certainly a decent first pass; however here are some suggestions.

First, rather than iterating over your game entities and their components in a heterogeneous fashion, why not consider a more homogenized way. Essentially your components are also maintained by a secondary object called a subsystem. Typically subsystems manage a particular type of component and so there is somewhat of a 1-to-1 relationship. This subsystem holds a reference to each component that was created that it manages in an internal vector and when your game loop iterates, you iterate like components together. This also allows you to perform updates on your components in an ordered fashion, regardless of how they may have been inserted into your entities component vector during creation time.

class Entity
class BaseComponent
class BaseSystem
class RenderComponent : public BaseComponent
class RenderSystem : public BaseSystem

Your game loop:

void CGameEngine::update(void timeSinceLastFrame) {
m_System1.update(timeSinceLastFrame);
m_System2.update(timeSinceLastFrame);
m_AnimationSystem.update(timeSinceLastFrame);
m_PhysicsSystem.updatePass1(timeSinceLastFrame);
m_System3.update(timeSinceLastFrame);
m_PhysicsSysetm.updateFinal(timeSinceLastFrame);
m_RenderSystem.update(timeSinceLastFrame);
};


As you can see above, our physics system (only as an example) performs two passes over it's components, and it could be that the system manages two different types of components as well but they need to be updated in a specific order. This approach allows us to step those entities in order. Additionally, if one subsystem finds an entity should be placed in a 'dont update me so often' state, that subsystem can move that component from one vector to another and maybe only update that secondary vector after a particular amount of time has passed while the remaining components are updated with each loop iteration.

This modular approach makes it easy to change and maintain logic and functionality in it's own little area of the world without imposing logic on your entity class during its update() method or your entity manager class during its update() method. To boot, this design typically is far more performing too.

HTH

Share this post


Link to post
Share on other sites
So basically if I was to derive AIcomponent from BaseComponent and a AISystem from BaseSystem and then gave (for example) each zombie an AI component instead of having the zombies update their AIcomponent themselves have the AISystem do it for them?


Also would it be better then during cleanup from one level to the next to delete the AISystem are create a new one or to just simply empty the vector and deleting the components after the entities have been deleted?

Share this post


Link to post
Share on other sites
Yes, that's generally how it's done. It's the a very practical way to ensure that the various systems are updated in the correct order.

Share this post


Link to post
Share on other sites
Ok, now is it practical to give components some components themselves? I'm mostly thinking about the AI components. Like being able to give an AI component another component that allows it to receive and processes sound events and the like.

Share this post


Link to post
Share on other sites
On 11/2/2011 at 6:08 AM, crancran said:

This subsystem holds a reference to each component that was created that it manages in an internal vector and when your game loop iterates, you iterate like components together. This also allows you to perform updates on your components in an ordered fashion, regardless of how they may have been inserted into your entities component vector during creation time.

Not to mention, instead of references, the subsytems could manage the actual component memory, making the processing of like components cache friendly with regard to data as well as code.

But taking your concept one step further, since the sub-systems own the components, anyone who needs access to them (including other components of the same object) could theoretically get it from the sub-systems. So technically, the entity object(with its member vector) doesn't even need to exist. It could, for instance be represented by a unique entity ID, and made up of all the various components filed with the sub-systems under that ID.

And this would remove the need for inheritance in the components (BaseComponent). (As your example is written, there's already no need for BaseSystem, but I could see that changing in a real implementation.)

Edited by vreality

Share this post


Link to post
Share on other sites
I don't want to comment the idea of having an entity object as I don't claim to have found the Holy Grail, but I think it's unclean to use strings to identify components, especially if all component types are known at compile time. Instead, use type erasure:





class entity
{
private:
class base_entity_wrapper
{
public:
std::type_info *id;
virtual ~base_entity_wrapper() {}
};
template< class Type >
class entity_wrapper
: public base_entity_wrapper
{
public:
entity_wrapper( Type object )
: obj( std::move( object ) ) ,
id( &typeid( Type ) ) {}
Type obj;
};
std::vector< base_entity_wrapper* > components;
//use a smart pointers instead
public:
template< class Type >
void add_component( Type object )
{
components.push_back( new entity_wrapper< Type >( std::move( object ) ) ); //currently not exception safe, use std::unique_ptr
}
template< class Type >
Type *get()
{
for( auto it = components.begin() ; it != components.end() ; ++it )
{
if( *(**it).id == typeid( Type ) )
return static_cast< component_wrapper< Type >* >( *it )->obj;
}
return 0;
}
};

//using:
e.add_component( CA() );
e.get< CA >();



You can increase performance drastically by using a handcrafted typeid, but I'm not sure whether posting one is worth the time.

Share this post


Link to post
Share on other sites
I like that idea VReality but how would I get them to coordinate their actions on the appropriate node? Would I make the node a component like I would have to the entity's vitals like health and other stats?

Share this post


Link to post
Share on other sites

I like that idea VReality but how would I get them to coordinate their actions on the appropriate node? Would I make the node a component like I would have to the entity's vitals like health and other stats?

You could take your entity's vital stats and break them out into their own components such as Health, Armor, Power or other components and then have each of those components managed by a their respective system. From a modular point of view it makes sense and keeps you from interweaving the code that manages an entity's health from their power, etc. Depending upon the number of vitals you give your entities, this could mean that an entity could have 10-20 components just for each vital stat. I haven't reached this level in my game yet; however, while I want to keep things modular and separated, I also do not want to overload the component-entity model with lots of small chunks that could easily be packaged into a single component. For example, a VitalsComponent and a VitalsSubsystem. Would be curious if others feel separate systems for each vital stat is the better design approach or a hybrid combination approach eases the game loop iterations.

As far as how they interact, this is still an area where I am trying to find a solution that I genuinely like. One option I have seen developers use is to keep pointers to the components in the entity itself and then use a method in the entity to retrieve components by type, such as:


template<class T> T* getComponentByType(std::string& sComponentType) { ... }


Some argue that some components have a tight relationship, such as your render component and your transformation component. Your rendered mesh can be placed on the scene if it doesn't know where to place the node, right? But maybe in this case, the render component is just dormant and doesn't render anything until a transformation is added? You could still implement this model using the above, but you'd need to naturally check for a NULL return pointer and react accordingly.

Another alternative is to use something like a message/callback system. I have seen where some developers prefer using a signal/slot concept where your component has event handlers you add as a part of that unique component's purpose. Then when your component is initialized, it registers those callback methods with a dispatcher and when events it is interested in are detected, those functions are invoked. This same concept could then be used for query methods as well so that the render component can invoke a signal and the transformation component acknowledges the signal by placing the position vector in the event return pointer and end. The render component could read the return value and either cache the vector or simply use it if performance is not a critical concern.

Another solution which is a bit more traditional is to follow the pattern where you have an interface defined for your event callbacks such as:


virtual void handleEvent(const CGameEvent& evt) { ... }


The downside to have a single callback point is that now you have to use a switch/if check to differ between the various notifications you may receive from the message subsystem. This is where the signal/slot concept is a bit more robust.

What have others done?

Share this post


Link to post
Share on other sites
On 11/2/2011 at 1:04 PM, crancran said:
On 11/2/2011 at 11:46 AM, jaeg said:

...how would I get them to coordinate their actions on the appropriate node? Would I make the node a component like I would have to the entity's vitals like health and other stats?

You could take your entity's vital stats and break them out into their own components such as Health, Armor, Power or other components ... however, while I want to keep things modular and separated, I also do not want to overload the component-entity model with lots of small chunks that could easily be packaged into a single component. For example, a VitalsComponent and a VitalsSubsystem.

The bottom line is that they definitely belong in a component. Not every game entity should have health. Not every game entity should have armor. Etc. Hence, that data and the functions which act on it should be componentized. If (near enough) everything that has health also has armor, then it probably makes sense for those stats to be in the same component.

There will be certain things which multiple components will be interested in, and those will need to be factored out. For instance, physics, locomotion, and AI, will all be interested in entity position. So position should be its own component, which might be managed by some sort of "spacial database" system.

Since all those other components depend on entity position, rather than each of them making the sub-system look it up every frame, you might opt to have them each cache a reference to it. In fact it's probably a good idea for each of them to verify its existence when they're created, since they can't work without it.

None of this conflicts with the idea of various components participating in message passing/signaling schemes, as they see fit.

And when it came to retrieving (or checking for) a component, rather than something like,

MyEntity.GetComponent(POSITION)
// or
MyEntity.GetPosition()

you'd use,

SpatialDatabase.GetEntityPosition(MyEntityID)
// along with stuff like
SpatialDatabase.GetEntitiesWithin(Location, Range)

 

Edited by vreality

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!