Component-Based Entity Help

Started by
9 comments, last by Kylotan 13 years, 10 months ago
I have read at great length on component-based entity solutions. And while I feel I have a good grasp on the concept, there are still small holes that I cannot seem to resolve. I hope others can help.

First, I have designed a simple model. I have created three critical classes that we're all probably very familiar with:

= Component
= Entity
= EntityManager

But what happens when specific components must be updated prior to other components during the same game loop iteration?

This may be where I have seen some introduce a "subsystem" concept. I have seen this used in two different ways in fact.

The first way is that when entities are added/removed via the entity manager, an event/callback is triggered and the subsystems react to this by inspecting the entity. If any component the subsystem cares about is found by iterating the component list, the component's reference is added to an internal list. Then the game loop ticks each subsystem in the desired logical order, not the entity manager itself.

The second way would be to have the subsystem actually created the component. When the entity is created, it calls a method on the specific systems necessary to return a reference to a Component. The subsystem would have already added it to its own internal list of components to manage for game loop manipulation. Similarly, during destruction of the entity, each component's subsystem would need to be asked to destroy the component.

I suppose its all a matter of preference but I find the first approach to give a very loose relationship between the component and the subsystem. The second way couples them closer together. But does this matter?

Another open gap for me is how granular is too much?

If we look at a house for example, it contains varying render components and physics components to be rendered and to prevent another entity from walking through its walls. But the house would not contain a health component, a mana component, and a GHealAbilityComponent that say a priest player would have.

Then would all characteristics and abilities be broken into their own component and subsystem classes such that I would see something like:

HealthComponent HealthSubsystem
ManaComponent ManaSubsystem
GHealAbilityComponent GHealAbilitySubsystem

Is this the right approach or should I look at trying to avoid a plethora of subsystems and simply have several core subsystems?

Also should game logic be in the subsystem and invoked by the event handlers on the component when specific events are received? And data elements, such as current health, max health, current mana, max mana, etc. Are these data elements to be maintained in the component class or properties on the owning entity in maybe a name-value-pair list?

Thanks in advance to any insight others may have.
Advertisement
A lot of this, I think, is personal preference. I use your second method of component creation. I got the idea from here. I seem to be linking to that thread more and more lately :)

If you follow the design suggested in the link, then the answer to your last couple of questions is that the data should be stored in the components. Ideally, data won't need to be shared or replicated, but this isn't really practical, so you'll need to allow components to access and talk to each other.

The best way I've come up with to think about this design is that entities are acted upon by their components. They don't act themselves. Entities are simply a means of keeping track of which components go together, and they "change" whenever one of their components changes.
Quote:Original post by Ariste
A lot of this, I think, is personal preference. I use your second method of component creation. I got the idea from here. I seem to be linking to that thread more and more lately :)


I have read over countless posts here with each of their own flavors. It's nice to see there isn't one good way to implement it; although I have to find one which suits me the best. Not always an easy task :P.

Quote:Original post by Ariste
If you follow the design suggested in the link, then the answer to your last couple of questions is that the data should be stored in the components. Ideally, data won't need to be shared or replicated, but this isn't really practical, so you'll need to allow components to access and talk to each other.


Correct.

A prime example would be when the health component receives TAKE_DAMAGE event, and the health finally reaches 0. When this happens, it triggers another event, PLAYER_DIED and the component responsible for showing the dead corpse would react to the event.

Keeping with this concept, the health component would be initialized in some fashion with the present character health along with their maximum health. Upon game startup, the terrain and game entities that are visible are drawn. Then the GUI is applied as an overlay.

How would the GUI obtain the value to draw of the current player's health? Would this require the GUI to use the HasComponent() and GetComponent() methods on the entity reference it has for the current player to get the values from the specified component or would I need to use messages to pass this data to the GUI?

Quote:Original post by Ariste
The best way I've come up with to think about this design is that entities are acted upon by their components. They don't act themselves. Entities are simply a means of keeping track of which components go together, and they "change" whenever one of their components changes.


That makes sense, especially if you're leveraging an entity for nothing more than a unique ID and a list of components that give the entity a particular set of behaviors that fire events to interact with other entities and other components.

In my script-based system, components are first registered to object 'classes'. The order in which the components are defined in script is the order in which they will fire at runtime. So, at a very basic level, some dependancy issues are solved by the user having a basic understanding of opengl.

Consider these two classes:

// script codeclass cThingOne  component coSprite,coColor,coShapeendclass cThingTwo  component coSprite,coShape,coColorend


cThingOne objects will work as one would normally expect, while cThingTwo objects will not.

During compile, when a class is built, each component in turn checks its own critical dependancy requirements. The script won't compile if certain dependancies are overlooked, and a corresponding error message is generated.

Some dependancies I don't consider to be critical, such as the order of coShape and coColor in the above example. Going to that depth of dependancy checking is going too far, imo. So, the compiler allows the user to make those kinds of mistakes.

Later, when an object is created at run time, dependancies have already been resolved, and its time to simply register the object with each component registered to its parent class.

Each component creates a custom data record for each registered object, initializes the record, and passes a pointer to the record back to the object.

In response, the object builds its own record containing references back to the component and to the Custom Data pointer it was just given. It adds that record to its own internal component array.

At render time, each object iterates through its internal component array, calls the Virtualized Run method for each component, while passing the original Custom Data pointer.

// Object Pascal codefor x := 0 to High(FRenderComp) do  FRenderComp[x].Comp.Run(Self,FRenderComp[x].Data);


Going this way allows an object to quickly iterate through its own registered components, without having any knowledge of exactly what the components do or why they do it. Also, the component is spoon-fed the exact data it needs, and doesn't need to waste time looking anything up.

In my experience, this is more common than components iterating through a list of all registered objects. Components that must iterate through objects take the extra step of adding each registered object to an internal list for later use.

Anyway, good luck on your project =)



--- "A penny saved never boils."
The way I solved this was to design components carefully. If any one component is dependant upon another I make sure that component gets refactored and rewritten into such a way that the dependency no longer exists.

Game development blog and portfolio: http://gamexcore.co.uk
If you have a type with "Manager" in the name, it's a candidate for refactoring.
Here's how I'm doing it, which isn't too far away from your method. As always, different games require different approaches.

I have just one Component class, but multiple system classes. A component contains a list of systems it installs and a collection of properties that it modifies.

So one component (this is a scifi space ship game) might install PhaserSystem and register itself as a modifier for PHASER_FIRE_RATE, PHASER_MAX_CHARGE, etc.

As for update ordering, there's not really any notion of dependence. Can you give an example of a component that needs to update before another component?

@return0: I know it's considered a code "smell" but I have an EntityManager class as well and see no reason to refactor it, nor can I think of a better name.

Maybe it will help if I show a bit of my source code and see if that may help as I am still confused on some implementation details.

Component Class
// Component.hclass Component : public IEventListener{public:  //! Destructor  virtual ~Component();  //! IEventListener override  //! Handles dispatching event to specific handler in subclassed components.  void OnEvent(Event& e);protected:  //! Internal method allows subcomponents to register handler callbacks  //! Used for message dispatching between components  //! EventHandler is a function pointer typedef   //! with a signature of "void Function(Event& e);"  void RegisterHandler(const string sEventType, const EventHandler& handler);private:  //! Event Handler Map  //! Map is keyed off sEventType  HandlerMap m_Handlers;};

HealthComponent Class
// HealthComponent.h//class HealthComponent : public Component{public:  HealthComponent(float fMaxHealth, float fCurrentHealth);  virtual ~HealthComponent();  //! Data Accessors  float GetMaxHealth()      { return m_fMaxHealth; }  float GetCurrentHealth()  { return m_fCurrentHealth; }  //! Handlers  void OnDamage(Event& e);  void OnHeal(Event& e);private:  float m_fMaxHealth;  float m_fCurrentHealth;};// HealthComponent.cpp//void HealthComponent::HealthComponent(float fMaxHealth, float fCurrentHealth){  m_fMaxHealth = fMaxHealth;  m_fCurrentHealth = fCurrentHealth;  RegisterHandler("OnDamage", &HealthComponent::OnDamage);  RegisterHandler("OnHeal",   &HealthComponent::OnHeal);}void HealthComponent::OnDamage(Event& e){  // TODO: OnDamage logic}void HealthComponent::OnHeal(Event& e){  // TODO: OnHeal logic}


When it comes to drawing a 2D panel that displays the player's name and health, how does this 2D panel obtain the value from the HealthComponent?

If I were to write the following:

m_pPlayerEntity->GetComponent<HealthComponent>()->GetMaxHealth();
m_pPlayerEntity->GetComponent<HealthComponent>()->GetCurrentHealth();

Does this violate the composition idea?

Some claim it is acceptable for a component to ask the owning entity to return a reference to another owned component should the two components need to operate on one another. But in my eyes, this again puts a strong link on the two components, does it not.

Wish I could overcome this hurdle in my mind.
Your wish is granted:

You can overcome this hurdle, because ....there ....is ... no... hurdle....

Its perfectly fine for components to expose or request information from other components. By definition, they use "interfaces". The goal is encapsulation, not seclusion.

So, code that interface!
--- "A penny saved never boils."
Quote:Original post by crancran
Some claim it is acceptable for a component to ask the owning entity to return a reference to another owned component should the two components need to operate on one another. But in my eyes, this again puts a strong link on the two components, does it not.

That's exactly how I do it.

The thing is, certain dependencies just exist, whether you want them to or not. If your UI widget needs to know about the player's health, then there is a dependency. You can try to mask it with message passing, but all this does is obfuscate the dependency and your solution to it. It's better to acknowledge that the dependency exists and provide an appropriate interface for dealing with it.

This topic is closed to new replies.

Advertisement