0
\$\begingroup\$

I want to design the entity system of my game in a way such that entities are modular, easily modified without affecting other entities, and finally easy to add new types of entities. So ideally some version of component based design.

In some parts of the code I want to deal with entities generally and abstractly, for example "update all entities" and "render all entities", however in other parts I want to deal with specific types of entity, like when a Soldier entity is fighting a Tank entity.

The problem is some entities need attributes which others do not. For example a soldier needs HP but a missile do not.

How do I keep entity attributes modular, and avoid adding every attribute in the game to the Entity object?

\$\endgroup\$
  • \$\begingroup\$ I'm a bit confused - you say you want a component based design, but then you talk about a soldier "class." In a component-centric model, you wouldn't have a soldier class, just an entity with a set of components that combine to make it soldier-ish. For the HP example, you might have a "Health" component that tracks HP of characters and destructible props. If your missile doesn't need it (eg. you can't shoot them out of the air) then you don't attach that component. \$\endgroup\$ – DMGregory Jul 26 '16 at 15:47
  • \$\begingroup\$ I want to know how to store attributes which need to be accessed by multiple different components but without storing superfluous attributes. For example, some entities need hitpoints while others don't, so how are hitpoints stored as to have it available to those who need it but not waste memory space in those who do not need it? \$\endgroup\$ – Anonymous Entity Jul 26 '16 at 16:51
  • \$\begingroup\$ The data is in a component; if your entity that represents the player needs hit-points, create a hit-point component, add it to the 'player' entity, and don't add it to the missile entity? \$\endgroup\$ – Vaillancourt Jul 26 '16 at 17:07
  • \$\begingroup\$ So does every entity have a null hit-points component by default? Then every time anyone needs access you have to do if entity.hp != null \$\endgroup\$ – Anonymous Entity Jul 26 '16 at 17:36
  • 1
    \$\begingroup\$ That's one (naive, IMHO) way to do it. Depends on the language you're using. You should take a look how Artemis does it if you're in Java. t-machine.org has also a lot of stuff on the subject as well. You can also take a look at the component model of Unity. \$\endgroup\$ – Vaillancourt Jul 26 '16 at 17:59
1
\$\begingroup\$

How do I keep entity attributes modular, and avoid adding every attribute in the game to the Entity object?

If you embrace the "entity is just a bag of components" concept, then this can be fairly simple. You package up related attributes into a component, and attach that component to certain entities but not to others. Attaching the component can be as simple as stuffing a pointer/reference to the new component into a list or map or something stored in the entity; the entity doesn't need to know it might have a HitPointComponent* specifically, just that it contains Component*, the base class of all components:

struct Entity {
  std::map<std::type_index, Component*> m_components;

  template<typename ComponentType>
  void attachComponent (Component* component) {
    std::type_index key(typeid(ComponentType));
    m_components.emplace(key, component);
  }

  template<typename ComponentType>
  bool hasComponent() {
    std::type_index key(typeid(ComponentType));
    return m_components.find(key) != std::end(m_components);
  }
};

(type_index provided as a simple example, not a suggestion that it is the "best" approach, in particular because it requires you always know the component types at compile-time.)

In some parts of the code I want to deal with entities generally and abstractly, for example "update all entities" and "render all entities"

I'd say you don't need to do this. Rather than operate on "all entities," instead operate on "all components of a given type." It's fairly common in component-based entity design to have a component actually belong to a system, some kind of container that is responsible for the creation, deletion and updating of all components of a particular type (for example, the implied HitpointSystem in my above code example).

Thus you might have a VisualizationSystem which hands out VisualizationComponents which describe how some object might look or is otherwise presented in the game. Non-visible entities don't get such a component. To render, the VisualizationSystem simply does something like:

for(auto && visualizationComponent : m_allVisualizationComponents) {
  renderer.draw(visualizationComponent);
}

This sort of design tends to use the "list of components associated with the entity" that attachComponent and the like modify purely as a way to test for the presence of a component with an entity, which is why it can be fairly simple. There's no need in this style of design to do "for each entity, for each component in that entity, update it."

however in other parts I want to deal with specific types of entity, like when a Soldier entity is fighting a Tank entity.

I'm still not sure what specifically you have in mind here for this kind of interaction that requires knowing about the type of entity. So I'll say that in general it might be better to push this kind of thing into data instead of into the type system.

For example, say you have a CombatantComponent to indicate that a given entity can be involved in combat iterations with other combatants (maybe you store your hitpoints here too). Suppose also the purpose of knowing whether the entity is a "soldier" or a "tank" is because a tank gets a defensive bonus versus a solider when exchanging blows. This typing information can be expressed directly in the CombatantComponent as a enumeration (UnitType) which you switch on to apply bonus damage (et cetera). Or it can be expressed less directly as an enumeration indicating the damage types a CombatantComponent can use and the damage types it gets bonuses or penalties to upon receipt.

Thus a tank might be defined as a entity with a combat component that has a damage multiplier for the Bullet damage type of 0.1, so it takes 10% of any incoming bullet damage. But maybe it also has 2.0 multiplier for Corrosive damage as well.

\$\endgroup\$
  • \$\begingroup\$ So when someone dealing with the entity wants to access some attribute, they have to be preceded by if (hasComponent(...) != end()) every time? Great answer by the way. \$\endgroup\$ – Anonymous Entity Jul 26 '16 at 18:34
  • \$\begingroup\$ @AnonymousEntity You'd probably want a getComponent() method in there as well. You could call that and check for null. If you expect you will be doing that a lot for any given piece of code you can of course call it once and cache it to avoid some of that overhead and annoyance. There are also ways to build things so that you can provide stronger guarantees that all required dependency components are available (see, for example, what EntityX does on that front), although I'm not personally a huge fan of most implementations of that technique. \$\endgroup\$ – user1430 Jul 26 '16 at 18:58
  • \$\begingroup\$ Thanks, that makes me re-evaluate the whole prospect of ECS. There must be some nicer way of doing things while still having good modularization. \$\endgroup\$ – Anonymous Entity Jul 26 '16 at 19:22
  • \$\begingroup\$ @AnonymousEntity It really depends what you mean by "nice." It's fairly subjective. The ECS paradigm can be very nice by several metrics, but particularly in C++ there's a fair bit of ugliness you need to hide simply because C++ lacks the built-in language facilities to do some of it elegantly. \$\endgroup\$ – user1430 Jul 26 '16 at 19:28
  • 1
    \$\begingroup\$ @AnonymousEntity: yeah, ECS is not perfect, and there are other ways of building or structuring component-based engines that have their own pros and cons. That said, one possible solution to the complexity problem you're observing is just more abstraction ("there is no problem in computer science that cannot be solved with another layer of abstraction.. except for the problem of too many layers of abstraction"). e.g., if you are querying for attributes FooBar, have a getFooBar(entity) function that hides any checks, returns good safe default values, etc. Simple. \$\endgroup\$ – Sean Middleditch Jul 26 '16 at 20:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.