Allegro.cc - Online Community

Allegro.cc Forums » Game Design & Concepts » Component-driven design

This thread is locked; no one can reply to it. rss feed Print
 1   2 
Component-driven design
CGamesPlay
Member #2,559
July 2002
avatar

Today I read about PushButton Engine, which is an open source Flash-based game engine (if you want to discuss it, please start a new thread). What interested me was the idea of Components, which, as far as I can tell, have only really been discussed here[1].

Component-driven design is an alternative to inheritance-driven design. Both designs are object oriented, but with components, all game objects are formless entities with no properties of their own. The game developer can take these entities and apply components to them. Each component adds a set of properties and behaviors to the entity, and can interact with other components that have been applied. By selecting which components are applied to each entity and by adjusting the properties, you add form to the entity. To accomplish this, you can use templates to provide a default set of components and properties to apply to entities. Templates can specialize one another in a traditional inheritance pattern, and then an entity is an instantiation of a particular template.

Before I clarify this, let me show the example that the slides use. Slide 27 shows a subset of the template forest. On the left is a list of root templates, or those that don't specialize any others. The slide shows a list of specialized templates descending in a hierarchy to an example chicken template. Slide 28 shows the details of a template, including how it defines values for the properties. On slide 33, we see the chicken instantiated with a few instance-specific property values specified.

To reiterate, components define what properties exist, but set no values for them. Templates are parallel to the inheritance pattern's classes, in that they specialize one another in a hierarchy. Templates may set or change values for properties but are not required to. Finally, a template can be instantiated. Instances may additionally change the values for properties.

This concept for structuring wildly different types of objects is interesting to me. It's really only a small modification to the inheritance pattern. What are your thoughts/opinions?

References

  1. I am referring to the GDC 2002 paper. For your convenience, I have attached a PDF version of the slides. Note: the HTML version of the slides doesn't appear to work.

--
Tomasu: Every time you read this: hugging!

Ryan Patterson - <http://cgamesplay.com/>

SiegeLord
Member #7,827
October 2006
avatar

This appears to be the same as multiple inheritance. Components = base classes. Entities = child classes. Templates = also classes. Instances = instances of classes.

The only thing I note is the different language used... Or am I missing something here?

"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18
[SiegeLord's Abode][Codes]:[DAllegro5]:[RustAllegro]

Thomas Fjellstrom
Member #476
June 2000
avatar

I think its more like interfaces, or ObjC's interfaces+protocols.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

SiegeLord
Member #7,827
October 2006
avatar

Each component adds a set of properties and behaviors to the entity, and can interact with other components that have been applied.

This seems to be a bit more than just an interface though...

"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18
[SiegeLord's Abode][Codes]:[DAllegro5]:[RustAllegro]

Thomas Fjellstrom
Member #476
June 2000
avatar

Yeah, its like being able to compose or "mixin" behavioral objects at runtime.

Intriguing idea, but how does it scale? (dynamic properties aren't exactly "fast", especially if you're talking about thousands or tens of thousands of game objects).

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

CGamesPlay
Member #2,559
July 2002
avatar

SiegeLord said:

This appears to be the same as multiple inheritance.

Starting at slide 7, the presentation illustrates why multiple inheritance wasn't used.

One of the big paradigm shifts is moving from is-a to has-a: from "an actor is drawable and collidable" to "an actor has a drawing behavior and a collision behavior". The reason for doing this is to minimize the amount of C++ code used: by using modular behavioral blocks, they can be rearranged without refactoring the code.

Intriguing idea, but how does it scale? (dynamic properties aren't exactly "fast", especially if you're talking about thousands or tens of thousands of game objects).

The properties used in this system aren't dynamic. A component defines some properties, and those don't change. Their value comes from various places, though (the value could be modified by a template, then again by a further-specialized template, then again by the actual instance and only the instance's value would be used).

--
Tomasu: Every time you read this: hugging!

Ryan Patterson - <http://cgamesplay.com/>

Thomas Fjellstrom
Member #476
June 2000
avatar

The properties used in this system aren't dynamic.

What it looked like to me is theres a data store that stores the game objects which are blank, and the components add (mixin) properties.. If this happens using regular C++ inheritance, its not special at all.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

CGamesPlay
Member #2,559
July 2002
avatar

I tried to avoid using implementation details, but the implementation from the presentation had the components existing as C++ classes, and components existing as script classes. Components have no inheritance, and each is separate and independent. Templates are not defined in C++ code at all. In the presentation, the data store contains all of the templates in compiled form.

Components don't change at run time, but since the templates are not C++ code, the components used can be changed around without recompiling. Each instance has a set of the values that the components define. They aren't dynamic because the properties themselves won't change at run time.

--
Tomasu: Every time you read this: hugging!

Ryan Patterson - <http://cgamesplay.com/>

ReyBrujo
Moderator
January 2001
avatar

It is not really model friendly, if you only have separated classes with no relation.

The closest I can think of is the composite pattern, although it is not exactly the same. For starters, the objects don't provide properties.

--
RB
光子「あたしただ…奪う側に回ろうと思っただけよ」
Mitsuko's last words, Battle Royale

SiegeLord
Member #7,827
October 2006
avatar

Starting at slide 7, the presentation illustrates why multiple inheritance wasn't used.

Do they? They don't ever mention the concept. Considering the FUD in the first few slides of the presentation, the author might not even be aware of it existing. The thing on page 7 is a single inheritance tree. The thing on page 8 is a multiple inheritance tree. Just use a dynamic cast to determine which components the class has, and go from there. I guess their idea bypasses the need for the dynamic cast though... that could be a plus.

Quote:

One of the big paradigm shifts is moving from is-a to has-a: from "an actor is drawable and collidable" to "an actor has a drawing behavior and a collision behavior". The reason for doing this is to minimize the amount of C++ code used: by using modular behavioral blocks, they can be rearranged without refactoring the code.

So yes, they created a new language to speed up/automate the multiple inheritance process. It appears to me that they just altered the implementation of multiple inheritance, since C++'s one is so clunky. Doesn't change the fact that that is what it is though.

"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18
[SiegeLord's Abode][Codes]:[DAllegro5]:[RustAllegro]

Tobias Dammers
Member #2,604
August 2002
avatar

Isn't it just another way of implementing the "composite object" approach? If you ask me, this is not a very new nor a very special thought (though often a good idea); it's just that this kind of thing isn't taught as heavily in typical OO courses (maybe because inheritance is harder to grasp, or maybe because it feels more "elegant" or more "clever" at first glance). This kind of thing actually helps AVOIDING multiple inheritance and all the crap you get from over-using it: GameEntity HAS A EntityBehaviour, and GameEntity HAS A EntityAppearance instead of GameEntity IMPLEMENTS IEntityBehaviour and GameEntity IMPLEMENTS IEntityAppearance. Composing objects means you can override the components individually, with independent levels of specialization.

---
Me make music: Triofobie
---
"We need Tobias and his awesome trombone, too." - Johan Halmén

Thomas Harte
Member #33
April 2000
avatar

I think its more like interfaces, or ObjC's interfaces+protocols.

I was thinking the exact same thing. Possibly I'm missing the point, but how is this different from implementing the protocols you intend to support and then registering yourself as a delegate in the appropriate places?

Ummm, to quickly help make sense of that for C++ folk: implementing a protocol is not inheritance, it's just implementing methods with the correct names and arguments, setting yourself as a delegate is nominating an instance of a class (any class, inheriting from anything at all or nothing) to be issued with certain method calls; if the methods are implemented by the class then that's great, if they're not then the ObjC runtime deals with problem transparently.

EDIT: Oh, and NextStep/OpenStep/GNUStep/Cocoa also has a thing called an NSNotification; all objects that are interested in a particular event (identified by a string) register themselves with an NSNotificationCenter (or the default NSNotificationCenter if you really only want one) and provide a method to invoke if the event occurs. Subsequently, any other object can trigger the event by sending a message to the notification centre, thereby causing all interested objects to be notified.

EDIT2: Plus, a neat one I learnt recently: you can use an NSInvocation to create a plain selector (ie, method identifier — not a pointer, just something that can be compared at runtime more quickly than a string, though the two are interchangeable at runtime) from any method call by supplying default arguments for the other parameters.

Steve++
Member #1,816
January 2002

The discussion here seems to be leaning towards the negative. Sure, this kind of design can be done with C++. But C++ doesn't give you this framework out of the box. This may not be the best architecture in the world, but it's certainly a good foundation for game development. I, for one, will be implementing something like this as the foundation of my next Allegro game.

I really like the idea of composing game entities out of components, registering those components with the engine and just watching all the pieces work together in all sorts of configurations.

axilmar
Member #1,204
April 2001

So, instead of multiple inheritance, use composition? multiple inheritance is composition. The only difference is namespace merging. And I don't think managing callbacks is any different from managing virtual methods.

Steve++
Member #1,816
January 2002

Multiple inheritance and composition are completely different. Multiple inheritence is used to show that an object is a specialisation of more than one object type. Composition is used to indicate than an object contains a number of components, each one providing a distinct service. In a composition design, it's easy to plug in any combination of components into container objects without necessarily having to specialise those components.

Sure, with multiple inheritence you could make your game objects inherit from the components that you want to use and sure, you can use RTTI to figure out which of the components are inherited, but then you are limited to composing the functionality of your game objects in code and modifying them at runtime is definitely out of the question.

The one thing MI has going for it in this scenario is that you can easily integrate the objects within each game object. But in reality, you wouldn't want to do that too much because the various components are unlikely to have much to do with each other even when combined into the same game object. I envisage that each component would implement common behviour and lifecycle interfaces so that any gamestate changes can be communicated to them.

As an excercise, I may rewrite Trek Invaders using this type of design. It will be interesting to see how well it lends itself to customisation.

axilmar
Member #1,204
April 2001

Steve++ said:

Multiple inheritance and composition are completely different. Multiple inheritence is used to show that an object is a specialisation of more than one object type. Composition is used to indicate than an object contains a number of components, each one providing a distinct service. In a composition design, it's easy to plug in any combination of components into container objects without necessarily having to specialise those components.

How can you not do that with multiple inheritance? by inheriting from a class, each object can be made to provide a distinct service. You don't need to specialize it if specialization is not required.

Quote:

Sure, with multiple inheritence you could make your game objects inherit from the components that you want to use and sure, you can use RTTI to figure out which of the components are inherited, but then you are limited to composing the functionality of your game objects in code and modifying them at runtime is definitely out of the question.

Aha, you are talking about run-time composition of objects. Indeed, that's something that MI can not do, but it is not because MI can not do it, it is because MI is completely unrelated to run-time object composition.

Comparing apples and oranges is not a good thing.

Quote:

The one thing MI has going for it in this scenario is that you can easily integrate the objects within each game object. But in reality, you wouldn't want to do that too much because the various components are unlikely to have much to do with each other even when combined into the same game object. I envisage that each component would implement common behviour and lifecycle interfaces so that any gamestate changes can be communicated to them.

As an excercise, I may rewrite Trek Invaders using this type of design. It will be interesting to see how well it lends itself to customisation.

How will you handle polymorphism?

Using a series of 'ifs' onto the runtime type of an object is a bad approach that does not scale well.

ReyBrujo
Moderator
January 2001
avatar

Oh, and NextStep/OpenStep/GNUStep/Cocoa also has a thing called an NSNotification; all objects that are interested in a particular event (identified by a string) register themselves with an NSNotificationCenter (or the default NSNotificationCenter if you really only want one) and provide a method to invoke if the event occurs. Subsequently, any other object can trigger the event by sending a message to the notification centre, thereby causing all interested objects to be notified.

Now you are talking about the observer pattern.

--
RB
光子「あたしただ…奪う側に回ろうと思っただけよ」
Mitsuko's last words, Battle Royale

CGamesPlay
Member #2,559
July 2002
avatar

I believe I misunderstood one of the core facets of this design: templates cannot register event handlers. Templates provide no code themselves, they only provide values for property fields. In this way, templates are analogous to a record in a database. A template can import a component to provide behaviors (and a specialized template imports its parent's components in addition to those it includes itself), but does not include any code. This makes the design significantly different from the inheritance pattern, and makes the game objects a database (as the slides mention).

--
Tomasu: Every time you read this: hugging!

Ryan Patterson - <http://cgamesplay.com/>

Steve++
Member #1,816
January 2002

axilmar said:

Aha, you are talking about run-time composition of objects. Indeed, that's something that MI can not do, but it is not because MI can not do it, it is because MI is completely unrelated to run-time object composition.

Comparing apples and oranges is not a good thing.

Actually, I'm not the one comparing apples and oranges. And this time you're lowering the bar to win the debate. The documents referenced above clearly state that one of the motivations behind the architecture is runtime composition of game objects.

Quote:

How will you handle polymorphism?

Using a series of 'ifs' onto the runtime type of an object is a bad approach that does not scale well.

I imagine each type of component will have its own global (within the game manager) collection with which each component instant registers. As events occur that are relevant to a specific component type, the game manager will simply process the collection for that type. I can't see a series of ifs being used here.

Axilmar, you seem to be very resistant to paradigm shifts, so much that you put forward arguments that just make us all scratch our heads. We've both been here long enough for me to know you weren't always like this. What gives? Is everything going alright with you?

axilmar
Member #1,204
April 2001

Steve++ said:

Actually, I'm not the one comparing apples and oranges. And this time you're lowering the bar to win the debate.

I am not here to win anything.

Quote:

Actually, I'm not the one comparing apples and oranges.

I never said that you did.

Quote:

The documents referenced above clearly state that one of the motivations behind the architecture is runtime composition of game objects.

But the documents also talk about why multiple inheritance is not appropriate for this job. So, the person who wrote that compares apples and oranges.

Quote:

I imagine each type of component will have its own global (within the game manager) collection with which each component instant registers. As events occur that are relevant to a specific component type, the game manager will simply process the collection for that type. I can't see a series of ifs being used here.

What you describe above is not polymorphism, unless those components are polymorphic themselves.

Quote:

Axilmar, you seem to be very resistant to paradigm shifts, so much that you put forward arguments that just make us all scratch our heads. We've both been here long enough for me to know you weren't always like this. What gives? Is everything going alright with you?

I am ok. I am not resistant to real paradigm shifts. I just don't see any paradigm shift here. I only see confusion.

Take the so called polymorphism mentioned above, for example: dispatching events to a component type is not polymorphism. A type is polymorphic if it has subtypes with overloaded methods.

As a (sort of) computer scientist with an interest in programming languages and software engineering, these things bother me.

Tobias Dammers
Member #2,604
August 2002
avatar

axilmar said:

How can you not do that with multiple inheritance? by inheriting from a class, each object can be made to provide a distinct service. You don't need to specialize it if specialization is not required.

Object composition allows you to de-specialize. Consider the following scenario: You have a number of entities in your game. Each entity has a draw() method, and an update_logic() method. Most of the game objects draw themselves by blitting a single sprite to the back buffer; some, however, require an extra sprite (say, a turret that moves independently); others draw themselves procedurally. It makes sense to have all objects that draw themselves the same way from a common base class. The same goes for the logic: Most game object act like solid bodies: they move according to Newton's laws, they bounce when colliding, etc.; others, however, follow a different movement pattern. Again, it makes sense to have a common base class for each type of movement.
But now we have a problem: Multiple inheritance doesn't allow us to freely combine different behaviours and appearances - we can't derive from SPRITE_DRAWING_ENTITY and SOLID_ENTITY at the same time, because they share a common base class (ENTITY) and thus both provide the same methods. MI, however, only allows combining base classes with mutually exclusive sets of members.
This is where composition comes into play - instead of defining the behaviour and appearance members in the base class directly, we put them into dedicated classes, each with its own polymorphic hierarchy, and give the base class a member variable for each. Thus, ENTITY HAS A BEHAVIOUR; ENTITY HAS AN APPEARANCE; SPRITE_DRAWING_APPEARANCE IS AN APPEARANCE; SOLID_MOVEMENT_BEHAVIOUR IS A BEHAVIOUR. Since both components are polymorphic, this also allows run-time composition of objects.

The word 'paradigm shift' however is out of line here, since composition is about as old as polymorphism when it comes to object-oriented design. It's just that because it's easier to implement and looks less 'clever', people tend to forget that it is a useful tool (often the best), and favour multiple inheritance.

---
Me make music: Triofobie
---
"We need Tobias and his awesome trombone, too." - Johan Halmén

axilmar
Member #1,204
April 2001

But now we have a problem: Multiple inheritance doesn't allow us to freely combine different behaviours and appearances - we can't derive from SPRITE_DRAWING_ENTITY and SOLID_ENTITY at the same time, because they share a common base class (ENTITY) and thus both provide the same methods. MI, however, only allows combining base classes with mutually exclusive sets of members.

What about virtual base classes? you can combine SPRITE_DRAWING_ENTITY and SOLID_ENTITY if they both inherit from ENTITY as a virtual base class.

Quote:

This is where composition comes into play - instead of defining the behaviour and appearance members in the base class directly, we put them into dedicated classes, each with its own polymorphic hierarchy, and give the base class a member variable for each.

Aha. So components ARE polymorphic after all. They need not be put in component lists.

Quote:

Thus, ENTITY HAS A BEHAVIOUR; ENTITY HAS AN APPEARANCE; SPRITE_DRAWING_APPEARANCE IS AN APPEARANCE; SOLID_MOVEMENT_BEHAVIOUR IS A BEHAVIOUR. Since both components are polymorphic, this also allows run-time composition of objects.

That's good design. I would do it like that, except when a derived behavior depends from the derived appearance or vice versa. Then multiple inheritance is better than composition.

Quote:

The word 'paradigm shift' however is out of line here, since composition is about as old as polymorphism when it comes to object-oriented design. It's just that because it's easier to implement and looks less 'clever', people tend to forget that it is a useful tool (often the best), and favour multiple inheritance.

That's my opinion as well. Renaming something and then marketing it as new is silly.

CGamesPlay
Member #2,559
July 2002
avatar

axilmar said:

Aha. So components ARE polymorphic after all. They need not be put in component lists.

The system used in Dungeon Siege does not have any polymorphic components.

I think the core difference between this pattern and object composition is that objects have no code and are only the sum of their property sheet in the editor. In object composition, you can add components to an object, but the object also has its own code base.

--
Tomasu: Every time you read this: hugging!

Ryan Patterson - <http://cgamesplay.com/>

SiegeLord
Member #7,827
October 2006
avatar

I think the core difference between this pattern and object composition is that objects have no code and are only the sum of their property sheet in the editor. In object composition, you can add components to an object, but the object also has its own code base.

So it's a subset of multiple inheritance idea that disallows the final object to add any code. As axilmar said, this may be better implemented (allows for runtime composition), or looks a little different, but it's still multiple inheritance called a different name.

"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18
[SiegeLord's Abode][Codes]:[DAllegro5]:[RustAllegro]

CGamesPlay
Member #2,559
July 2002
avatar

SiegeLord said:

So it's a subset of multiple inheritance idea that disallows the final object to add any code.

Except for the fact that it's composition, and not inheritance, yes.

--
Tomasu: Every time you read this: hugging!

Ryan Patterson - <http://cgamesplay.com/>

 1   2 


Go to: