7

I'm creating a component-based game object system. Some tips:

  1. GameObject is simply a list of Components.
  2. There are GameSubsystems. For example, rendering, physics etc. Each GameSubsystem contains pointers to some of Components. GameSubsystem is a very powerful and flexible abstraction: it represents any slice (or aspect) of the game world.

There is a need in a mechanism of registering Components in GameSubsystems (when GameObject is created and composed). There are 4 approaches:


  • 1: Chain of responsibility pattern. Every Component is offered to every GameSubsystem. GameSubsystem makes a decision which Components to register (and how to organize them). For example, GameSubsystemRender can register Renderable Components.

pro. Components know nothing about how they are used. Low coupling. A. We can add new GameSubsystem. For example, let's add GameSubsystemTitles that registers all ComponentTitle and guarantees that every title is unique and provides interface to quering objects by title. Of course, ComponentTitle should not be rewrited or inherited in this case. B. We can reorganize existing GameSubsystems. For example, GameSubsystemAudio, GameSubsystemRender, GameSubsystemParticleEmmiter can be merged into GameSubsystemSpatial (to place all audio, emmiter, render Components in the same hierarchy and use parent-relative transforms).

con. Every-to-every check. Very innefficient.

con. Subsystems know about Components.


  • 2: Each Subsystem searches for Components of specific types.

pro. Better performance than in Approach 1.

con. Subsystems still know about Components.


  • 3: Component registers itself in GameSubsystem(s). We know at compile-time that there is a GameSubsystemRenderer, so let's ComponentImageRender will call something like GameSubsystemRenderer::register(ComponentRenderBase*).
    Observer pattern. Component subscribes to "update" event (sent by GameSubsystem(s)).

pro. Performance. No unnecessary checks as in Approach 1 and Approach 2.

con. Components are badly coupled with GameSubsystems.


  • 4: Mediator pattern. GameState (that contains GameSubsystems) can implement registerComponent(Component*).

pro. Components and GameSubystems know nothing about each other.

con. In C++ it would look like ugly and slow typeid-switch.


Questions: Which approach is better and mostly used in component-based design? What Practice says? Any suggestions about (data-driven) implementation of Approach 4?

Thank you.

Community
  • 1
  • 1
topright gamedev
  • 2,617
  • 7
  • 35
  • 53

2 Answers2

2

Vote for the third approach.

I am currently working on component-based game object system and i clearly see some of additional advantages of this approach:

  • The Component is more and more self-sufficing subentity as it depends only on a set of available subsystems (i presume this set is fixed in your project).

  • Data-driven design is more applicable. Ideally, this way you may design a system where components are completely defined in the terms of data but not C++.


EDIT: One feature i thought about while working on CBGOS. Sometimes it is convenient to have ability to design and construct subsystemless passive components. When this is on your mind the fourth approach is the only way.

Keynslug
  • 2,676
  • 1
  • 19
  • 20
  • I agree with you that these advantages are very valuable. But the first one has another side: no `Component` reusability between projects (with different sets of `Subsystems`). Recomposition of subsystems in a single project becomes an issue, too. There can be hundreds of `Components`, rewriting them all is a tedious task. I believe that second advantage can be reached in other approaches, too. – topright gamedev Oct 18 '10 at 12:06
  • Agree with your point. At a time i am introduced with CBGOS design not as much as i wish to. But the work i ran into dealing with this got me to the following considerations: *1.* Design subsystem interfaces at the most abstract way so the set of subsystems would change slightly across different projects. *2.* Prefer by-message interaction between components above all and cut out interface dependencies where it's possible. – Keynslug Oct 18 '10 at 12:22
  • It can work out. I met people at gamedev.net forums that use this approach, too. – topright gamedev Oct 18 '10 at 15:20
  • 1
    About "subsystemless passive components". I found extremely useful to divide `Components` into 3 different hierarchies: Model, View, Controller. ModelComponent - contains just data and some useful routines. (ComponentTitle mentioned in the question for example) ControllerComponent has method update and performs something at every loop. ViewComponents - for rendering, audio etc. There are 3 corresponding `Subsystem` types. We know at compile-time what kind of `Component` we are searching for in the `GameObject`, so searching becomes 3 times faster. And we achieve a better decomposition. – topright gamedev Oct 18 '10 at 15:29
  • Did not think this way about components decomposition. It must be valuable. – Keynslug Oct 18 '10 at 19:40
1

My approach was to implement the proxy pattern within each subsystem. As each subsystem is only interested in a subset of the total components each entity may contain, The proxy stores pointers to only the components the system cares about, eg, a motion system only cares about position and velocity, so it needs a proxy which stores two pointers, to those components. If the entity is missing one or more of those, then the subsystem will ignore it. If both components are present, then a proxy node is created and added to an internal collection. It is also useful for the proxy to store the unique identifier value for the entity, so that proxies may be added/removed in constant time from each subsystem, should it be necessary.

In such a way, should an entity be required to be removed from the engine, a single message containing the id of the entity can be sent to every subsystem. The proxy can then be removed from each subsystem collection independently.

Ian Young
  • 1,712
  • 1
  • 16
  • 33