Component based system, doing it right?

Started by
18 comments, last by CogOfHate 11 years, 1 month ago

So I'm trying to make an more sophisticated component system, most have been not as large, but this time around I'm aiming at highly flexible one so this is my first attempt at making anything decent.

I'v grasped the basic aspects of Components, and the only thing that ever has bothered me about it is communications between all of the components, especially in "larger" ( not 3xA large but larger than Pong or Tetris ) "games".

Now I'v came up with an design that I feel comfortable with ( In some aspects anyways ), It's inspired from Don't Starves Lua-Component system as well as a topic ( it can be found here: http://gameprogrammingpatterns.com/component.html#when-to-use-it ).

Basically the Entity ( Container Object ) have an array/vector/map of components that is indexed by an enum-table called "ComponentID", which is where all the components are defined as integer ids.

This helps to sort each component based on it's "priority" which in turn makes sure they are called in the right order as well as clarifies in code what it refers to.

Entity has under 'protected' two methods called "hasComponent(ComponentID id)" & getComponent(ComponentID id), the later is a template'd function that dynamically casts the right class depending of the ID.

The Component Interface has access to these via friend-keyword seeing as those functions should be exposed but not publicly. Here is the syntax I'm currently targeting within my components, this is from the component "Movable" - which handles movement and coordinates.






/* Inside the Movable.cpp */
if ( sf::Keyboard::isKeyPressed( sf::Keyboard::Key::Down ) )
{
	float ny = y + MOVEMENT_SPEED * dt;
}
if ( sf::Keyboard::isKeyPressed( sf::Keyboard::Key::Up ) )
{
	float ny = y - MOVEMENT_SPEED * dt;
}
if ( sf::Keyboard::isKeyPressed( sf::Keyboard::Key::Right ) )
{
	float nx = x + MOVEMENT_SPEED * dt;
}
if ( sf::Keyboard::isKeyPressed( sf::Keyboard::Key::Left ) )
{
	float nx = x - MOVEMENT_SPEED * dt;
}

/* If an entity does have an Physics Component. */
if ( entity.hasComponent( ComponentID::PHYSICS ) )
{
	/* Request it as such. */
	Physics* p = entity.getComponent<Physics*>( ComponentID::PHYSICS );
	/* Check if it's solid and there is no tile/terrain collision */
	if ( p->isSolid() && !p->terrainCollision( nx, ny, entity ) )
	{
		x = nx;
		y = ny;
	}
}
else /* Just move it as such. */
{
	x = nx;
	y = ny;
}

I know it's not exactly perfectly decoupled, but this is my first time around and I though it was an easy and flexible way of getting data from other components that said component needs, such as Graphic-Component needs coordinates where to draw the sprite, or in this case, if an Physics-Component exists then check if the object is solid and make sure it doesn't collide.

Now here is where you come in, I'm wondering if this is really a good, or acceptable way of handling communications/interactions between components? Am I doing the component pattern completely wrong or am I on the right track?

Kind regards, Moonkis.

EDIT:
If anyone is wondering the function: "Entity::getComponent(ComponentID id)" will be using static_cast<> because I'v read that it performs WAY better than dynamic_cast<> and I am confident I'm trying to cast an correct type ( the map<ComponentID, IComponent*> looks like that ).

Advertisement
I do my comp/ent system this way. My entity holds a map of components keyed on the component enum type, which means you don't have to loop through components to find the one you want, works really well for me. I guess if you wanted more than one similar-type components, you could hold a vector of components in the map, wouldn't take much to change

You both are using an aggregation pattern for components only. The component system stores more then just components in the game object it also stores attributes, and it is these you should set which in turn the components can access. If you need inter component communication you should send a message to the currently connected entity and let it pass it done to the rest of it's components, if this component shouldn't handle it send it to all currently active game objects and let them handle it like the internal message passing works.

A component shouldn't need to know anything about any other component or be passed any real information other then the entity in it's update function, see this article and all its previous posts on it http://www.altdevblogaday.com/2011/09/23/the-game-entity-part-v-future-ponderings/

I use fast string hash functions in my engine to do the look up of components and attributes that are stored on the entity because name based indexing is easier to read whilst developing. And it also allows me to add components and attributes multiple times under different names.

If your entity contained a position attribute you could have fixed this like this:


void KeyboardInputComponent::Update(Entity& entity)
{
    Vector3& position = entity.getAttribute( Hash.HashString( "position" ) ).getValue<Vector3>(); //Or how you store your position
   if ( sf::Keyboard::isKeyPressed( sf::Keyboard::Key::Down ) )
   {
       float ny = y + MOVEMENT_SPEED * dt;
   }
   if ( sf::Keyboard::isKeyPressed( sf::Keyboard::Key::Up ) )
   {
       float ny = y - MOVEMENT_SPEED * dt;
   }
   if ( sf::Keyboard::isKeyPressed( sf::Keyboard::Key::Right ) )
   {
       float nx = x + MOVEMENT_SPEED * dt;
   }
   if ( sf::Keyboard::isKeyPressed( sf::Keyboard::Key::Left ) )
   {
       float nx = x - MOVEMENT_SPEED * dt;
   }
  
    position += Vector3(nx, ny,  0.0f);
}
 
void PhysicsComponent::Update(Entity& entity)
{
    Vector3& position = entity.getAttribute( Hash.HashString( "position" ) ).getValue<Vector3>(); //Or how you store your position
    //Do your collision here with the position you have updated before
}
 
//Or
void PhysicsComponent::respondToPositionChangedMessage(const PositionChangedMessage& msg)
{
   Vector3 position = msg.getPosition(); //Message is sent from the keyboard component
    //Do collision
}

Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, theHunter, theHunter: Primal, Mad Max, Watch Dogs: Legion

I minimise the use of getComponent by using a message system. I use it for communicating between the components of an entity as well as between entities. There's a message object for each type of message. The components register with the entity what kind of message they are interested in. Then it's just pDest->SendMessage(pFrom, message).

No need to care about which component, if any, is going to receive it. In fact, multiple components might pick up the message and do different things.

One thing - you don't get a return value. You might get a message back, later. For basic information I use tags on the entities, an idea I took from Unity.

The advantage is almost total decoupling of different component types. They don't even need to include each other's headers to communicate, just the messages they send or receive.

I minimise the use of getComponent by using a message system. I use it for communicating between the components of an entity as well as between entities. There's a message object for each type of message. The components register with the entity what kind of message they are interested in. Then it's just pDest->SendMessage(pFrom, message).

No need to care about which component, if any, is going to receive it. In fact, multiple components might pick up the message and do different things.

One thing - you don't get a return value. You might get a message back, later. For basic information I use tags on the entities, an idea I took from Unity.

The advantage is almost total decoupling of different component types. They don't even need to include each other's headers to communicate, just the messages they send or receive.

This is what the linked topic also teaches ( sending a message ) problem is I'm having a hard time seeing it, there are so many types of messages that I don't know how to represent them, Objects? Integers? Strings? How would they look like? And more importantly how should they be parsed/interpreted? Does each component prase/interpret differently? Seems very un-efficient.

There's a message object for each type of message. The components register with the entity what kind of message they are interested in. Then it's just pDest->SendMessage(pFrom, message).

This part I can see...somewhat. How would that look like? I mean I'm assuming that "Message"-objects are derived from a base-class. Then I'm guessing that somewhere it needs to be casted to the appropriate message? How can the Components identify or Entity what type of message it is and how it should be casted?

You both are using an aggregation pattern for components only. The component system stores more then just components in the game object it also stores attributes, and it is these you should set which in turn the components can access. If you need inter component communication you should send a message to the currently connected entity and let it pass it done to the rest of it's components, if this component shouldnw't handle it send it to all currently active game objects and let them handle it like the internal message passing works.

A component shouldn't need to know anything about any other component or be passed any real information other then the entity in it's update function, see this article and all its previous posts on it http://www.altdevblogaday.com/2011/09/23/the-game-entity-part-v-future-ponderings/

I use fast string hash functions in my engine to do the look up of components and attributes that are stored on the entity because name based indexing is easier to read whilst developing. And it also allows me to add components and attributes multiple times under different names.

If your entity contained a position attribute you could have fixed this like this:


void KeyboardInputComponent::Update(Entity& entity)
{
    Vector3& position = entity.getAttribute( Hash.HashString( "position" ) ).getValue<Vector3>(); //Or how you store your position
   if ( sf::Keyboard::isKeyPressed( sf::Keyboard::Key::Down ) )
   {
       float ny = y + MOVEMENT_SPEED * dt;
   }
   if ( sf::Keyboard::isKeyPressed( sf::Keyboard::Key::Up ) )
   {
       float ny = y - MOVEMENT_SPEED * dt;
   }
   if ( sf::Keyboard::isKeyPressed( sf::Keyboard::Key::Right ) )
   {
       float nx = x + MOVEMENT_SPEED * dt;
   }
   if ( sf::Keyboard::isKeyPressed( sf::Keyboard::Key::Left ) )
   {
       float nx = x - MOVEMENT_SPEED * dt;
   }
  
    position += Vector3(nx, ny,  0.0f);
}
 
void PhysicsComponent::Update(Entity& entity)
{
    Vector3& position = entity.getAttribute( Hash.HashString( "position" ) ).getValue<Vector3>(); //Or how you store your position
    //Do your collision here with the position you have updated before
}
 
//Or
void PhysicsComponent::respondToPositionChangedMessage(const PositionChangedMessage& msg)
{
   Vector3 position = msg.getPosition(); //Message is sent from the keyboard component
    //Do collision
}

Components holds their variables they are using, Entity is basically just an shell where components lives, it doesn't own any attributes other than an array of components, rather is collected under the same roof. "Movable" haves the attributes float x,y nx, ny it's responsibilities is moving and keeping track of where it is.

Your approach is very interesting though, how does a component send a message? How does a component know what type of message it is? How does it reach the right components if they share they same base-class and lives under an anonymous array?

I don't parse messages, I send fixed message types, which are structs containing the message data as members. Each message type has a string identifier, typically just the name of the struct.

For example, a controller component might send a Move message, which contains a vector. The entiIty has a hash map of function pointers using the message identifier as the key. When the physics component gets the message, it is just as though a function had been called.

Overall, it's no more expensive than looking up a component directly and calling a function.

I don't parse messages, I send fixed message types, which are structs containing the message data as members. Each message type has a string identifier, typically just the name of the struct.

For example, a controller component might send a Move message, which contains a vector. The entiIty has a hash map of function pointers using the message identifier as the key. When the physics component gets the message, it is just as though a function had been called.

Overall, it's no more expensive than looking up a component directly and calling a function.

Interesting so there is no base-struct? But don't you have to make a new send function EVERY time you want to implement a new message-type? There is a lot of information that is missing which confuses me a lot more instead of helping me understand.

I'm guessing the Entity has the send-function? Or it doesn't? Are there even an base class for components? How does it look like?

I just don't get it...what calls what and which holds what functions?


EDIT:


Maybe the send message is a template'd method? Something along the lines of:

template<class T>
virtual void broadcast(T msg);

/* Calling it as */
broadcast<SoundMessage>(msg);



Moonkis: your system looks ok to me. Ignore anybody who tells you that "a component system should ...." because there is no 'True' way to do this. The way you are handling it is a lot like the way that Unity handles components - and good games are shipped with Unity on a regular basis.

I attempt to keep component-to-component references to a minimum, but I think that forbidding them entirely just serves to overcomplicate the system. Sometimes you really do want one component to be able to call a function on another component and get a response, and forcing that to go via message passing will create more brittle code than the alternative. I certainly don't think that's the goal you should aim for when you're still relatively inexperienced with component-based entities.

One small recommendation: don't do this:


if ( entity.hasComponent( ComponentID::PHYSICS ) )
Physics* p = entity.getComponent<Physics*>( ComponentID::PHYSICS ); 

Instead, do this:


Physics* p = entity.getComponent<Physics*>( ComponentID::PHYSICS );
if (p) { 

No point doing the lookup twice (once to check if it's there, once to get a pointer to it). Just return either the pointer or null.

Moonkis: your system looks ok to me. Ignore anybody who tells you that "a component system should ...." because there is no 'True' way to do this. The way you are handling it is a lot like the way that Unity handles components - and good games are shipped with Unity on a regular basis.

I attempt to keep component-to-component references to a minimum, but I think that forbidding them entirely just serves to overcomplicate the system. Sometimes you really do want one component to be able to call a function on another component and get a response, and forcing that to go via message passing will create more brittle code than the alternative. I certainly don't think that's the goal you should aim for when you're still relatively inexperienced with component-based entities.

One small recommendation: don't do this:


if ( entity.hasComponent( ComponentID::PHYSICS ) )
Physics* p = entity.getComponent<Physics*>( ComponentID::PHYSICS ); 

Instead, do this:


Physics* p = entity.getComponent<Physics*>( ComponentID::PHYSICS );
if (p) { 

No point doing the lookup twice (once to check if it's there, once to get a pointer to it). Just return either the pointer or null.


Yes, sounds good to me! And I'll definitely change that.

Though for later/other projects I really want to get into this message systems but I'v been goggling like crazy and I haven't found any good example other than:

sendMessage(message);

But it still leaves me with a lot of questions like I'v asked in the posts above.

I don't parse messages, I send fixed message types, which are structs containing the message data as members. Each message type has a string identifier, typically just the name of the struct.

For example, a controller component might send a Move message, which contains a vector. The entiIty has a hash map of function pointers using the message identifier as the key. When the physics component gets the message, it is just as though a function had been called.

Overall, it's no more expensive than looking up a component directly and calling a function.

Interesting so there is no base-struct? But don't you have to make a new send function EVERY time you want to implement a new message-type? There is a lot of information that is missing which confuses me a lot more instead of helping me understand.

I'm guessing the Entity has the send-function? Or it doesn't? Are there even an base class for components? How does it look like?

I just don't get it...what calls what and which holds what functions?


EDIT:


Maybe the send message is a template'd method? Something along the lines of:

template<class T>
virtual void broadcast(T msg);

/* Calling it as */
broadcast<SoundMessage>(msg);



Correct on all counts. And don't take anything I'm saying as being THE way to do it. There are many implementations and this is just mine.

SendMessage is a templated function in Entity. Messages are just structs. There's no base message type. There is a base component type, to allow Entity to hold base component pointers.

In practice it looks like this. I use macros to simplify some of the syntax.


// messages.h

struct Message_SetPosition 
{
	DECLARE_MESSAGE(SetPosition) // This creates a function returning the identifier to use in the hash map. 

 
	Message_SetPosition(const Maths::Point2D& position_) : position(position_) {}

	Maths::Point2D position;
};

// physics.h

class PhysicsComponent : public ComponentBase
{
public:
	void RegisterHandlers(Entity* parent); // Called once when the component is created.
	
        /// ...

	void HandleMessage(Entity* from, const Message_SetPosition& m);

	/// ...

};

// physics.cpp

void PhysicsComponent::RegisterHandlers(Entity* parent)
{
	REGISTER_HANDLER(parent, PhysicsComponent, Message_SetPosition) // Registering a member function pointer with the parent Entity.
}


// Inside some other component

pEntity->SendMessage(parent, Message_SetPosition(p));

This topic is closed to new replies.

Advertisement