4
\$\begingroup\$

I'm using a hybrid of DDD (domain-driven development) and an entity-component system. That is to say:

  • I have an Entity class with a list of Components, and the ability to add/remove/get components
  • I have component instances like HealthComponent (health and is-alive), DrawComponent (position + display), etc.
  • System instances (like DrawSystem) that find a bunch of components and operate on them

I also have "domain objects" like Player and Monster, which subclass Entity. Their constructors usually add a bunch of components.

Since I'm a fan of the DDD idea of objects represent business/game entities, I model my classes around that. For a roguelike, my Player class has methods like:

  • Attack(Monster)
  • GetX and GetY (delegates to the position component)
  • LevelUp
  • RegenerateHealth
  • And others

It seems to me that the components are a white-box internal detail; they shouldn't be known to any outside objects. In fact, domain objects should operate on other domain objects (eg. Player.Attack(Monster)).

Most (if not all) of my actual game code operates on entities, not components. My tests usually do something like:

  • Create an entity
  • Grab a certain component
  • Verify that it meets expectations

In general, this raised a question in my mind: isn't this a violation of encapsulation? Aren't components just an internal implementation detail of entities (that could change or disappear at any time)?

On the other hand, I have a lot of delegation, which seems useless (like Player.getX() is just Player.get(DisplayComponent).getX()).

This seems confusing. Can someone provide some clarity? What is the right mix of DDD, encapsulation, and entity/component composition?

\$\endgroup\$
  • 1
    \$\begingroup\$ Why not move all the logic into components (systems). RegenerateHealth could be for example method on HealthComponent Attack method could be on CombatantComponent and take another CombatantComponent as argument. \$\endgroup\$ – Kikaimaru Dec 4 '13 at 15:45
  • \$\begingroup\$ @Kikaimaru because I would only call RegenerateHealth on the player. Entities need to attack, because it's a combination of position components (are you in range?) and health components (are you alive?) and other attributes (strength, etc.) \$\endgroup\$ – ashes999 Dec 4 '13 at 16:54
  • 1
    \$\begingroup\$ ...And that's exactly where you begin to go wrong, which is what others are trying to tell you here. Nothing goes onto the Entity except categorical components, wherein all the real logic exists. You are meant to encapsulate Regenerate within a Health component which describes both state and functionality relating to Entity health. Now when anything affects health, whether it be internal or external to the Entity itself (including unit tests), these may simply be accessed by referencing that Entity's appropriate component property, in this case, Entity.Health. \$\endgroup\$ – Engineer Dec 4 '13 at 17:19
  • 1
    \$\begingroup\$ It sounds to me like you would be happier with an OO mixin design instead the wild and untamed composition via component systems design. In many ways components are inimical to classes and their design paradigms and exist partially to fix problems with deep class hierarchies. For example, even a component having its own processing outside of a system raises many questions as to why was it even turned into a component since it's obviously acting like a class? \$\endgroup\$ – Patrick Hughes Dec 4 '13 at 21:29
  • \$\begingroup\$ take a look to this: cowboyprogramming.com/2007/01/05/evolve-your-heirachy :) \$\endgroup\$ – Thelvyn Dec 5 '13 at 8:23
7
\$\begingroup\$

I'm afraid you've got this a bit wrong. The entire reason for component-based entity systems is that you should be favouring composition over inheritance. You should not be using inheritance over Entity. Entity exists as a composed piece of data / functionality, consisting of Components only. It is, so to speak, intended to be a final class, i.e. no extensions.

Processing which you currently have in constructor functions, is meant to be performed by factories. Your factory's build() method should take an object of parameters used for the construction of your individual entity types. This means you can create any permutation you wish, at runtime, provided the appropriate parameters are supplied to that method. Parameters may be either static (e.g. loaded from a data file) or runtime-constructed based on certain conditions (such as a the player entering a factory or store in-game, and specifying a new unit type). You want to decouple your logic related to building an Entity, from the Entitys themselves. By using inheritance hierarchies, you are instead locking yourself down to compile-time decisions.

Very little of your code should be operating on Entity itself. Entity should be a highly generalised access point for specific data/functionality sets; thus, you ought to have your Components as sets of related functionality, for instance, transform, physics, ai, inventory, renderer (if applicable) and so on. You can store these either as individual properties or as keyed values in a hashtable. The reason that you are specifying these as such, is that they are self-contained. For instance, you wouldn't need an AI command queue if the object had no AI. So we pack these together, and then it also makes access easier within related sets of functionality (due to shared scope).

Lastly, do not concern yourself overly with encapsulation at the Entity level. Your primary goal is to make your entities and their components as accessible as possible between different subsystems, at all times. Simply make them publically accessible, and worry about accessibility later if it really still seems important. But at the Component level, there may be methods and data members which will only ever be used internally and thus may be marked private from the get-go.

\$\endgroup\$
  • \$\begingroup\$ This is a good critique of one facet of my component/entity system, which I intentionally decided. Although it has validity, it has other issues (eg. I have components which use lambdas/methods -- I'm not moving those into a scripting language, it's too much work). It doesn't really address the domain side, which is what I primarily intended feedback on. \$\endgroup\$ – ashes999 Dec 4 '13 at 16:56
  • 3
    \$\begingroup\$ @ashes999 It does, implicitly: By definition, these two paradigms will not play nicely together. I'm suggesting that you pick one, DDD or CBES, and drop whichever you see as less suitable. Inheritance locks you into compile-time decisions, defeating attempts to integrate a flexible CBES, recommended for games. Which do you think is going to fit your problem domain better? I suggest spending more time reading up on CBES, and the reasons for their existence as a de facto games industry standard. I've explained the salient points, but I don't think you're seeing the bigger picture... not yet. \$\endgroup\$ – Engineer Dec 4 '13 at 17:07
  • 2
    \$\begingroup\$ @ashes999 If you don't like this answer, vote it down and attempt to clarify your question. \$\endgroup\$ – MichaelHouse Dec 4 '13 at 17:37
  • 1
    \$\begingroup\$ @Byte56 so true. Stacks are a great place for specific questions but you are in no way guaranteed to like the answers you get and clarifying the question is the best way of directing that process. \$\endgroup\$ – Patrick Hughes Dec 4 '13 at 21:19
  • 2
    \$\begingroup\$ I came back to this answer three years later and it makes a lot more sense. Thanks! \$\endgroup\$ – ashes999 Mar 31 '16 at 11:29

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.