Welcome, Guest. Please login or register.
Did you miss your activation email?

Login with username, password and session length

 
Advanced search

1305816 Posts in 58326 Topics- by 49434 Members - Latest Member: LeeHamilton

July 26, 2017, 02:37:25 am

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)Component-Based Architechture: Getting Away from Inheritance
Pages: [1] 2 3 4
Print
Author Topic: Component-Based Architechture: Getting Away from Inheritance  (Read 30473 times)
Theotherguy
Level 1
*



View Profile Email
« on: December 26, 2009, 06:19:16 pm »

EDIT: just realized this should probably be in Tutorials

Hello, I thought I would post a little article on this forum about component-based architectures -- since a lot of people have been asking me questions about this recently, and the concept really blew me away when I first heard of it.

Without further adieu, let's get into it.

Part I: Adventure Boy, a Case Study

Setting up an Inheritance Tree
Let's say you, an amateur developer, are making a game called Adventure Boy. You are working on the game with a team of designers. Let's say its an adventure game in which the player, Adventure Boy, travels through levels picking up items of importance and talking to NPC's. Your design team tells you that each level is a set of simple maze-like rooms separated by doors. In each room there can be NPCs and items. Items are collectible, and you can talk to NPCs and trade items with them.

How would you go about setting up a game engine to get the design team's vision of Adventure Boy to work?

If you were a developer versed in C++, C#, or Java, you might think about setting up a class hierarchy. You could have a separate classes for rooms, doors, NPCs, items, and Adventure Boy himself.

Because you are a clever developer, you decide to set up your class hierarchy like a tree, so you can reuse as much code as possible using inheritance. The sub-classes of this inheritance tree know about their super-classes, but the super-classes don't know about their subclasses. This is generally a good practice in software development, and is often encouraged in introductory courses.

So, you think of all the things that need to be in the game, rooms, doors, NPCs, items, and Adventure Boy. You think of all the things they have in common, and things that are different. You decide to divide the class hierarchy into two branches: things that are static, and things that are dynamic. Things which can move, and things which can't.

So you make three classes, Entity, Static_Entity, and Dynamic_Entity. Inheriting from Static_Entity, you put the classes Room_Entity and Door_Entity. You then construct your levels out of Room_Entities and Door_Entities. Inheriting from Dynamic_Entity, you place Talking_Entity and Item_Entity. Inheriting from Talking_Entity you have NPC_Entity and Player_Entity. Finally, you make Static_Entity and Dynamic_Entity inherit from Entity.



What you have done is made a very tight classification system in which all the functionality of the game can be made while re-using as little code as possible. After several months of development, you have a basic system that the designers are happy with.

The Problem
Then, your design team decides that at some point in the game, the player meets a talking door which will give the player an item before letting him pass through. They say this is an incredibly important part of the story, and is a necessary feature for the game.

What do you do? By now, you have this gigantic system set up, full of assumptions about the properties of entities in your game, and what they can do. Fundamental to your current game engine is the assumption that doors are static. They cannot talk. They cannot move. They cannot interact with the player.

So what can you do to resolve this issue? You have a few options here.

First, you could hack it. You could make the door an NPC which appears in front of an actual door, and which dies as soon as you trade an item with it, allowing you to pass through a door. But this is incredibly messy, and is not very robust.

Secondly, you could decide that doors can talk to things anyway. You could move all the functionality from Talkable_Entity into Door_Entity. However, now you have copied code, eliminating your original goal of re-using as much code as possible.

Thirdly, you could move the functionality from Talkable_Entity up the tree into just Entity. That way, all entities could talk to you. But that doesn't make sense either. Why should we assume that all entities in the game can talk to the player? Why do we even have a Talkable_Entity class in the first place?

Fourthly, you could re-design your entire class hierarchy and move things around just to accommodate this feature.

Giving up in frustration, you pick one of these routes and hack it together, while completely destroying your beautiful, tight inheritance tree.

The Solution
If you're a developer in an object oriented language, this is a problem which  you have likely faced. I definitely faced it in some of my earlier attempts at programming a game. I'd end up with hundreds of classes with confusing dependency, and lots of redundant code as new features needed to be added. The problem is, writing a good class hierarchy relies on strict planning, and its not often possible to plan out perfectly what features your game will need before you begin coding it.

There is a much better way to handle this issue, but before you embark on it, you're going to have to abandon your training and preconceptions about object-oriented languages, and open your mind to new possibilities.

Let's go back to our original problem. Getting doors to talk. Let's say that you try solution number 3: moving the Talkable functionality up to the Entity class. The problem with this, I will re-iterate, is that it assumes all entities can talk. To solve this problem, we can add a flag to the Entity class called "canTalk." Then, when we instantiate a Door, we set this boolean flag to true. In our game logic, whenever an entity has to talk to another entity, it checks the boolean flag "canTalk," and if its true, the player can engage in conversation with it, and otherwise not. Then, we can stick all the Talkable functionality into a single class which we will call "Talkable." Then, every entity can have a pointer to a Talkable object that is NULL if it can't talk, and is a valid Talkable class if it can.

Code:
Class Entity
{
   ....
   ...
   Talkable talkSystem;
   boolean canTalk;

   ...
   public Entity(boolean canTalk, Talkable talkSystem){
        this.canTalk = canTalk;
        this.talkSystem = talkSystem;

   }
   public Talkable getTalkSystem()
   {
       if(canTalk)
       return talkSystem;

       else throw some exception...

   }

..
}

What have we done here? We have eliminated inheritance, and instead emulated it using a totally different approach. Before, when we had a class that inherited the "Talkable_Entity" class, we knew that it WAS a "talkable" entity, and that only "talkable" entities could talk. This is called an is-a relationship.

Now, all entities either HAVE the ability to talk or not. This is called a "has-a" relationship. "Talkable" entities are only defined by whether or not they HAVE the ability to talk, not by a hard coded abstraction.

The "Talkable" class we created is called a component. A component is an interchangeable, self-contained system that adds functionality to an Entity.

Now, why not do this to all of our game functionality? We can eliminate the cumbersome inheritance tree altogether by putting all of our functionality in components. We can have a Passable component for doors, a Bounds component for rooms, a Collectable component for items, etc. etc. What we are left with is a typeless game architecture in which all the functionality of the game is in one class, the Entity class.

Part II: The Component Based Architecture
This style of programming is called a Component-Based Architecture. ALL of your game entities are of the same class, and they are defined by what components they have, rather than what type they inherit from. Rather than re-using code in super-classes, you re-use code by giving similar entities similar components, and rather than hiding information from your super-classes, you hide the components from one another, keeping them as self-contained as possible.

In a component-based architecture, we have two principal types: Entities and Components. (In all my games, I call these classes Ent and Sys, but that's a rather confusing convention that I have kept around out of tradition). Entities have a list of Components, and all Components have a standardized interface for communicating with Entities and with other components.

Every single thing in our game can be an Entity, from the player to the items, to the levels, even to effects in our level and scripts that affect other entities.





Remember before when we had a boolean flag and a pre-defined Talkable component to tell our Entity whether or not it was capable of talking? This is a bit clunky, and a bit of a mess. You wouldn't want to have a special boolean flag for every single component. So here is how I manage all of my components.

Managing Your Components
First, a look at the Component class from which all components will inherit. This is the component class from a game I am making called Moonwalk (again I use the terms Ent and Sys for Entity and Component):

Code:
public abstract class Sys
    {
        /* All components have an Entity as a parent*/
        public abstract Ent getParent();
        public abstract void setParent(Ent p);

        /* All components have a unique type identifier*/
        public abstract int getType();

        /* All components do something to affect game logic in an update method
           The Entity class calls update() on all of its Components.*/
        public abstract void update(GameTime delta);

        /* All components can be expected to draw information to the screen
           The Entity class calls render() on all its Components.*/
        public abstract void render(GameTime delta);

        /* If a component has assets attached to it (like images, or sounds),
           then it can be expected t load them in this method. When an Entity
           is initialized, it loads all the assets of all its components.*/
        public abstract void loadAssets();

        /*All Entities can be removed from the game world. When they are removed,
          they call the die() method. The die() method calls all of the die() methods
          on every component, where cleanup can be performed, or special events can
          be triggered*/
        public abstract void die();


    }

Each Component has a unique type identifier, that tells us what type it is, or at least some way of generating one. Usually, when I code a game I keep a list of integers assigned to every possible component, and update that list as I add more features to the game. However, it might be better to generate the type identifier on the fly, based on the type of the component.

For instance, here is the big list of type identifiers for components in BeeStruction:
Code:
//Unique component identifiers
        public static int SPRITE        = 0;
        public static int ANIMATION     = 1;
        public static int AISYSTEM      = 2;
        public static int PHYSBODY      = 3;
        public static int EXPLOSION     = 4;
        public static int ENTRELEASER   = 5;
        public static int MATERIAL      = 6;
        public static int BEEHIVE       = 7;
        public static int CARRYABLE     = 8;
        public static int HEALTH        = 9;
        public static int ATTACK        = 10;
        public static int TEAM          = 11;
        public static int VOICE         = 12;
        public static int FLEE          = 13;
        public static int DEATH_TRIGGER = 14;
        public static int ANI_SYSTEM    = 15;
        public static int WAYPOINT      = 16;
        public static int FLAMMABLE     = 17;
        public static int PARTICLE      = 18;
        public static int MECHAFARMER   = 19;
Then, in the Component interface, I have a method that returns the type identifier called "getType()".

Finally, in our Entity class, we have a hash table that stores all of the components and hashes them based on their unique identifier. To check an Entity to see what Components it has, we simply have to poll its hash table and see if it contains a Component with the given identifier.

So, if we wanted to have our player talk to any entity that has a Talkable component, we would have to do something like this:
Code:
// In our Entity class...
public boolean hasComponent(int identifier)
{
   return component_table.Contains(identifier);
}
public Component getComponent(int identifier)
{
   if(hasComponent(identifier))
     return component_table.Get(identifier);

   else return null;
}
Code:
 //In our Talkable Component
   public static int TALKABLE = 1;

  public void talkTo(Entity other)
  {
      
       if(other.hasComponent(TALKABLE))
       {
        
            // do stuff

       }
       else
       {
            //throw some exception, or do nothing.

       }
  }

EDIT:
As is mentioned in the comments it would probably be easier to instead hash based on class type, rather than by using integer constants:

Code:
public boolean hasComponent(Class<C> klass)
{
   return component_table.Contains( klass );
}

public C getComponent(Class<C> klass)
{
    final C component = component_table.get( klass );
   
    if ( component != null ) {
        return component;
    } else {
        return null;
    }
}
Making an Entity

Now, bringing it all together, we can make an entity simply by instantiating all of its required components, adding them to its hash table, and telling it to update and render in our game loop.

All of this may seem a bit complicated, but it gives us immense power over our engine, allowing us to change it and re-use it as we see fit.

Part III: Why Component-Based Architectures Rock

1. Scalability
Whenever we need to add new features, or change the way features work, all we have to do is make a new component, and the features are instantly in the game, automatically. No fussing with class hierarchies or dependencies, no hard-coded changes to the underlying engine, just new functionality. Since components are self-contained, they can be interchanged to create fantastic new entities. New types of entities can be created on the fly in our game by a procedural process, without even requiring any input from the programmer.


2. Re-usability

Games with the same component-based architecture can use each others components without any changes to the underlying engine design. As long as the interface remains consistent, you can take components from one game and put them into another extremely easily.

3. Flexibility
The Component-Based Architecture is incredibly flexible to different kinds of games and different strategies of storing and representing entities. One of the biggest perks of this architecture is that its easily applicable to data-driven entity design. You could stick all of your entities in consistent XML data files, which can be edited by designers. You could even have entities generated on the fly inside an editor or during gameplay.

4. Consistency
When all your game entities are instances of the same class, and all of your functionality has a standardized interface, it makes it much easier to keep track of what is going on in your game, and much easier to manage dependencies and capabilities. You can avoid all of the hassle of clunky inheritance trees and dependency diagrams and focus on core functionality. It can be a real production booster. In Component-Based architectures, game Entities are demoted to being linkers between various components of functionality.


Part IV: What Component Based Architectures Are Not

1. Multiple Inheritance: Multiple inheritance creates dependencies at compile time exactly as single inheritance does. With multiple inheritance, types of entities are hard coded and their functionality is determined when your code is compiled. In contrast, in a component-based architecture, "types" of entities are determined at run-time. Any entity may have any component. This can allow changes to be made to game entities without ever having to re-build and re-compile any code.

2. Mixins, or Interfaces: while interfaces and mixins introduce similar "has-a" dependencies to game entities, conceptually, they are not the same thing. Mixins and interfaces are used at compile-time to introduce guarantees of the existence of methods within your game entities. Like with multiple inheritance, types are hard-coded.

3. A Programming Language: You can implement component based architectures in many different programming languages using many different styles. A component-based architecture may be better suited for a particular language (such as SmallTalk or Objective C), but my point here was not to replicate the features of a particular language in another, but rather to show you that component-based architectures are possible in any language. The reason I chose Java and C# for my examples was because I am familiar with these languages.



Part V: Some Tradeoffs

Ah, but of course there are always trade-offs when choosing a particular architecture over another. The main problem with component based architectures is that they eliminate types, and therefore, type-checking. It can be extremely useful to know if a game entity in your environment IS a particular thing, rather than if it HAS a particular thing. It can make organizing your code much easier. However, it is possible to get around this using clever hacks, but you will still not get the kind of tight type-checking you will typically get with inheritance.

Secondly, you give away some of the richer features of inheritance. Communicating between components, and making components which inherit features from other components without code duplication can be difficult in this sort of environment. This is why I usually use a mix of inheritance and components in my games, making particular inheritance trees for each component. Otherwise, you may enter case-statement hell, where you are constantly checking to see if the entity you are dealing with has a particular collection of components.



I hope that this was somewhat helpful. I might be preaching to the choir here, but when I first heard this method of organizing a game engine I was completely astounded. I had struggled with the issues of inheritance for many years, and switching to a component based architecture made my productivity skyrocket, and made game programming much more enjoyable.
« Last Edit: January 02, 2010, 07:27:59 pm by Theotherguy » Logged

Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW Email
« Reply #1 on: December 26, 2009, 09:29:07 pm »

It looks like you're just re-implementing mixins, which are difficult to do in languages that don't support true multiple inheritance (C#/Java).  Multiple inheritance seems like a much cleaner way to implement this in C++.
Logged



What would John Carmack do?
Theotherguy
Level 1
*



View Profile Email
« Reply #2 on: December 26, 2009, 09:57:53 pm »

It looks like you're just re-implementing mixins, which are difficult to do in languages that don't support true multiple inheritance (C#/Java).  Multiple inheritance seems like a much cleaner way to implement this in C++.

You could indeed implement a component-based architecture using mixins. However, there are some key differences.

Mixins introduce dependencies at compile time. You have types which are declared to be using some set of mixins, and you would have a different type for each entity that essentially just declared a list of mixins that it was using.

Using my implementation, all entities are defined at run-time. It is essentially a typeless system.

The power of this is that a designer could generate new kinds of entities simply by using data files, or even a graphical editor, without any work being done by the programmer to give some entity a certain functionality. Components can be created, destroyed, and swapped out at runtime or in a data file without any changes to the code whatsoever. The choice of which game entities do what falls on the designer, not on the programmer -- which can be a very powerful thing.

EDIT:
Just to give you an example, here is the XML data file that defines an Entity in Moonwalk, a blue laser beam:

Code:
<?xml version="1.0" encoding="utf-8"?>
<EntDefinition xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <components>
    <component xsi:type="PhysicsBody">
      <pulledByGravity>true</pulledByGravity>
      <mass>0.001</mass>
      <I>0.4</I>
      <friction>0.4</friction>
      <restitution>0.15</restitution>
      <radius>0.05</radius>
      <isSensor>true</isSensor>
      <isBullet>true</isBullet>
      <addedToWorldOnInitialization>false</addedToWorldOnInitialization>
      <lastVelocity>
        <X>0</X>
        <Y>0</Y>
      </lastVelocity>
      <lastPosition>
        <X>0</X>
        <Y>0</Y>
      </lastPosition>
      <defaultdamping>1</defaultdamping>
      <defaultfriction>0.4</defaultfriction>
      <defaultRestitution>0.15</defaultRestitution>
      <isBox>false</isBox>
      <width>0</width>
      <height>0</height>
      <categoryBits>1</categoryBits>
    </component>
    <component xsi:type="Sprite">
      <rotatesAlignedWithZero>true</rotatesAlignedWithZero>
      <flipped>false</flipped>
      <textureAssetName>laserbeam</textureAssetName>
      <rot>0</rot>
      <scale>1</scale>
      <tint>
        <R>255</R>
        <G>255</G>
        <B>255</B>
        <A>255</A>
        <PackedValue>4294967295</PackedValue>
      </tint>
      <layer>0</layer>
      <rotspeed>0</rotspeed>
      <rotatesWithParent>false</rotatesWithParent>
      <simpleRotation>true</simpleRotation>
      <scaleX>0</scaleX>
      <scaleY>0</scaleY>
      <drawInRectangle>false</drawInRectangle>
      <flipsWithMotion>false</flipsWithMotion>
    </component>
    <component xsi:type="TouchDamage">
      <damage>1</damage>
      <maxLife>5000</maxLife>
    </component>
    <component xsi:type="Team">
      <team>0</team>
    </component>
    <component xsi:type="PEffect">
      <asset>bluething</asset>
      <looped>true</looped>
      <trailsParent>true</trailsParent>
      <randomTriggerFactor>0.1</randomTriggerFactor>
      <triggersUponDeath>true</triggersUponDeath>
    </component>
  </components>
  <filePath>laser.xml</filePath>
  <loadedFromFile>false</loadedFromFile>
  <spawnLocation>
    <X>0</X>
    <Y>0</Y>
  </spawnLocation>
</EntDefinition>

This XML data file is de-serialized directly into an Entity at runtime.

From this, we can see that the Bullet has a physics body, a sprite, a component for damaging things it touches, a team, a particle effect, etc. If I wanted to give the blue laser another component, all I would have to do is add a new component to its XML data file. I wouldn't have to change any of the types in the code, or add new functionality by declaring it to inherit from a mixin. If a designer wanted to, they could edit the properties of the laser in real time, adding and removing components, changing variables in the components, and watching the effects in the engine, all without doing anything to the code.
« Last Edit: December 26, 2009, 10:10:48 pm by Theotherguy » Logged

Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW Email
« Reply #3 on: December 26, 2009, 10:22:36 pm »

I stand corrected then, you've actually reimplemented Objective-C.

Objective-C is based on the idea that you can call any method on any object, whether it supports it or not (Smalltalk style object-orientation).  This basically looks what you're doing, except the languages you're using require you to do this manually.
Logged



What would John Carmack do?
Triplefox
Level 9
****



View Profile WWW Email
« Reply #4 on: December 26, 2009, 10:39:48 pm »

This is a fine summary. But I would point out that the key takeaway about entity components is not about language features. It is that entities do not deserve a prominent place in game architectures. They are best kept as a means of wiring up and providing uniqueness identifiers for other forms of dataflow - or in other words, they are the integration pathway for engine features against game rules. You can implement a similar architecture in just about any language with mutable state. Dynamic type systems and virtual methods happen to allow more fungible integration techniques, but they are not strictly necessary.
Logged

Theotherguy
Level 1
*



View Profile Email
« Reply #5 on: December 26, 2009, 10:48:10 pm »

This is a fine summary. But I would point out that the key takeaway about entity components is not about language features. It is that entities do not deserve a prominent place in game architectures. They are best kept as a means of wiring up and providing uniqueness identifiers for other forms of dataflow - or in other words, they are the integration pathway for engine features against game rules. You can implement a similar architecture in just about any language with mutable state. Dynamic type systems and virtual methods happen to allow more fungible integration techniques, but they are not strictly necessary.

Of course, I probably shouldn't have provided example code. The whole driving force behind the thing is separating entities from their functionality. You could do the same thing using a variety of different methods, and a language with built in message-passing and interface inheritance would definitely be helpful in implementing such a system.
Logged

Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW Email
« Reply #6 on: December 27, 2009, 12:57:57 am »

You could do the same thing using a variety of different methods, and a language with built in message-passing and interface inheritance would definitely be helpful in implementing such a system.

This is exactly why I pointed out Objective-C.  If you find yourself doing things like this, and they form a major part of your design, you may be using the wrong language for the job.

There are other considerations of course, and even more interesting solutions like using Obj-C just to implement the game objects, and providing an external interface (most likely in C) for other languages to interact with the Obj-C portion of the code.  Then you can take advantage of Obj-C message passing where you need it, and use some other language for everything else where you don't need it.
Logged



What would John Carmack do?
BlueSweatshirt
Level 10
*****

the void


View Profile WWW
« Reply #7 on: December 27, 2009, 02:16:56 am »

I get the point of what you're trying to explain.
But if you have the availability of multiple inheritance, why are you limiting yourself?

As triplefox pointed out, classes like your "Entity" are usually only for wiring everything together. In what I've found, polymorphism is the most commonly needed use.

Back to limiting yourself, why not create classes that inherit from this entity and contain a set of pre-determined components?
This helps separate the data. It makes it easier to work with, for one. This still follows the principal of re-using code.

Your numbering system is also flawed. Why go to such trouble when you could have each Entity hold a container of string-type classes? When the 'tell me if you have it' function is called, you simply iterate through the strings of the container to find matches. This would virtually eliminate the nasty need for memorization, albeit taking up a small amount more of memory.

Anyway, that's all from me. Just throwing out some programming theory, I'm sure someone improve my idea. You still explained the concept of components quite well, but you might want to constrict and tighten your explanation a bit more. You kind of danced around the concept of components rather than striking them right at the heart.

Good job nonetheless. The graphical charts you made up were a nice touch that I liked.
Logged

bateleur
Level 10
*****



View Profile
« Reply #8 on: December 27, 2009, 04:40:29 am »

But if you have the availability of multiple inheritance, why are you limiting yourself?

It's not limiting, though. Is it?

Components aren't an algorithm, they're really just a design pattern. Whether multiple inheritance would be a better choice will depend on your project. (In particular, it's less modular than components, so the larger the project the less inclined I'd be to use it.)

Logged

BorisTheBrave
Level 10
*****


View Profile WWW
« Reply #9 on: December 27, 2009, 05:46:20 am »

Man, why are all you guys so sceptical. I see a solid tutorial on component based design, and it's benefits. Admittedly, not much is mentioned on it's weaknesses. My main complaint is you cannot call a hypothetical case a case study :D.

Componenets != mixins as components are runtime, mixins are not (for some definitions of mixin, at least).

Components != Multiple inheritance as again, runtime.

Components != inerfaces as runtime, contains concrete member variables / implementations.

Components != Objective C as components are concrete. I note that ObjC seems to have a feature called "categories" that strongly resembles components.

The problem with components, is you do discard some of the benefits of inheritance. Suppose you want object to respond to being damaged in different ways. Do you make one health component that supports both ways, or two different components, and the "doDamage" routine knows of both. Or do you make your components have an inheritance structure, so that the doDamage routine can retrieve just the abstract component it wants. None of these really seem to respect the premise of components. So you either end up with component components, or you go with a system like objectiveC's message passing, or mixins, where method calls aren't tied to the components that will implement them.

Another problem with components (and many other solutions discussed), is when you want cross-component behaviour.
Logged
Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW Email
« Reply #10 on: December 27, 2009, 08:43:52 am »

Components != Objective C as components are concrete. I note that ObjC seems to have a feature called "categories" that strongly resembles components.

Categories really have nothing to do with this, unless you're using them in some very creative way.  Categories let you add methods to a class without using inheritance, and without altering the original class.

For example, in my Objective-C++ code I have category that adds methods to Cocoa's NSString to allow it to handle C++ strings.  Something like this:

Code:
@interface NSString (CPPStrings)

+ (NSString) stringWithCPPString:(std::string) cpp_string;
+ (NSString) stringByAppendingCPPString:(std::string) cpp_string;

@end

Where Obj-C fits this pattern is in its concept of message forwarding.  In Obj-C, if an object receives a method invocation that it doesn't understand, it can forward it to another object that might understand it.  This is essentially what's going on here, but in Obj-C, you get it for free.
Logged



What would John Carmack do?
Glaiel-Gamer
Guest
« Reply #11 on: December 27, 2009, 09:33:14 am »

Another problem with components (and many other solutions discussed), is when you want cross-component behaviour.

One of the problems I have with components, is lets say you have some RPG. All the main characters are people, so in a component architecture it's each character class is made up of the following components:

Person
Armor
Weapon
[specifics]

Then lets say you wanted to add boots to the characters. Do you want to have to go through and add all the boots to all the characters? That could easily be a pain if you have tons of classes of characters.

It makes sense to encapsulate all that stuff into a person component, so then Person is

Ragdoll Model
Armor
Weapon
Boots
etc..

and classes have a Person and then class specific stuff in them

It gets a little messy here, cause you need some weird way of doing component or entity inheritance which I thought the point of components was to get rid of stuff like this. Or you need components made up of components which starts to get really funky and complicated.

IMO it's easy enough to be "smart" with inheritance and "smart" with composition at the same time, and it feels much more natural that way. I just think this component stuff is coming from people who had a really bad experience with nasty trees with multiple inheritance and disorganization everywhere, but rather than moving somewhere in the middle they just decided to promote the extreme opposite as "amazingly better for everything". It's not. I like trees for organization, but component trees and inheritance trees both have their benefit and a mix of the 2 can be pretty cool
Logged
BorisTheBrave
Level 10
*****


View Profile WWW
« Reply #12 on: December 27, 2009, 09:42:17 am »

Yes, exactly. Components as described here are a way to specify class functionality without changing anything in the class itself. Sounded a bit like categories. I guess if they are just intended for extensions, you cannot add member variables, or inspect which ones are attached.

Anyway... Using message forwarding certainly makes implementing a component easier than in Java, where method forwarding must be done on a per method basis. But the rest of the component setup must be created in ObjC just like Java or C++, because either way you must choose which of several objects to forward the invocation to. I wouldn't say it's accurate to assume component based design is handled for you entirely by the ObjC runtime, i.e. that components are "reinventing" any aspect of ObjC.

The OP wasn't even suggesting using method forwarding, but rather explicit inspection of an objects components. I.e. I think his intention was that a given function, say, doDamage, would have knowledge that only objects with health components are affected. You don't go querying every component saying "do you handle doDamage", but instead jump straight to the one you want. This is quite different from message passing/forwarding.
Logged
Theotherguy
Level 1
*



View Profile Email
« Reply #13 on: December 27, 2009, 09:52:02 am »

Thanks guys, I have modified my original post to handle some of your complaints.
Your numbering system is also flawed. Why go to such trouble when you could have each Entity hold a container of string-type classes? When the 'tell me if you have it' function is called, you simply iterate through the strings of the container to find matches. This would virtually eliminate the nasty need for memorization, albeit taking up a small amount more of memory.

That's actually a fantastic idea that I hadn't thought of before. I was using the numbering system to make it easy to hash the components, but now that I think about it, I'm never going to have a large enough number of components for it to make a difference. Perhaps I can just use a hash map hashing strings to components.

Logged

Theotherguy
Level 1
*



View Profile Email
« Reply #14 on: December 27, 2009, 09:55:38 am »

One of the problems I have with components, is lets say you have some RPG. All the main characters are people, so in a component architecture it's each character class is made up of the following components:

Person
Armor
Weapon
[specifics]

Then lets say you wanted to add boots to the characters. Do you want to have to go through and add all the boots to all the characters? That could easily be a pain if you have tons of classes of characters.


Yes, that can be tedious. In developing beestruction, for instance, when we would add a feature which would be used with most of the entities in the game, we had to go through and add the component to every entity's data file. Other times we were forced to give certain components functionality that they were never intended to have purely for the sake of laziness.

EDIT:

Specifically in this case though, what I usually do if I want to encapsulate components for similar entities is I make an entity corresponding to the "canonical" person, which has a bunch of default components. I make a deep copy of this canonical person for all the others, and add, remove and modify components as necessary. Then, if I want something to be inherited by all people, I change the components in the canonical person.

I should mention this makes much more sense if you're also using data-driven development, and all your entities are in data files separate from the code.
« Last Edit: December 27, 2009, 10:34:48 am by Theotherguy » Logged

Triplefox
Level 9
****



View Profile WWW Email
« Reply #15 on: December 27, 2009, 10:33:34 am »

It makes sense to encapsulate all that stuff into a person component, so then Person is

Ragdoll Model
Armor
Weapon
Boots
etc..

and classes have a Person and then class specific stuff in them

Alternatively, you could accept that the real problem is that you don't know yet what you want on a Person, and instead of dicking around with static class modelling techniques, push the equipment information into a properties pattern via a hashmap or associative array, giving you dynamism at the expense of some runtime overhead and fewer compile checks.
Logged

raigan
Level 4
****


View Profile
« Reply #16 on: December 27, 2009, 10:57:29 am »

and classes have a Person and then class specific stuff in them
...
It gets a little messy here, cause you need some weird way of doing component or entity inheritance which I thought the point of components was to get rid of stuff like this.

I agree with others that this isn't actually a problem, since you would have some sort of tool/mechanism to generate the data for you (e.g the Person would be an "archetype" to use Scott Bilas' terminology). Although I agree that in this case you're back to having a hierarchy, the idea is that the hierarchy exists only as a data description (i.e you could manually add boots data to each type, a hierarchical description that is used to automatically inject boots data is merely a convenience ) and the actual runtime data is all "flat".

I also agree that such a system is probably overkill much of the time; I have yet to see a simple/reasonable explanation of how you should approach things like shared data (e.g entity position) that isn't stupidly convoluted, there seem to be lots of generic messages floating all over the place when direct function calls would do just as well.
Logged
zantifon
Level 0
**


View Profile WWW
« Reply #17 on: December 27, 2009, 05:20:32 pm »

I'm a big fan of this kind of programming design for larger projects. The real benefit, as was mentioned, is when you want to define entities in data files. The entire point for me was that any designer could completely redefine an object without touching the code. If you use a class hierarchy, you'd need a programmer just to add a property to an object. But this way you just add a line to some text file. Heck, you don't even need to restart the program, let alone recompile it. It makes iteration much, much faster.

Of course this is only a benefit if the only designer isn't also the programmer...
Logged
Klaim
Level 10
*****



View Profile WWW
« Reply #18 on: January 02, 2010, 04:49:01 pm »

Hi Theotherguy, nice explaination Smiley

You might (all of yours) be interested in this : http://www.ogre3d.org/forums/viewtopic.php?f=1&t=53302
Logged

http://www.klaimsden.net | Game : NetRush | Digital Story-Telling Technologies : Art Of Sequence
StudioFortress
Level 0
***



View Profile WWW Email
« Reply #19 on: January 02, 2010, 05:38:23 pm »

Rather then using integer constants to represent your values you could do it by type. i.e.

In the entity class:
Code:
public boolean hasComponent(Class<C> klass)
{
   return component_table.Contains( klass );
}

public C getComponent(Class<C> klass)
{
    final C component = component_table.get( klass );
    
    if ( component != null ) {
        return component;
    } else {
        return null;
    }
}

usage:
Code:
hasComponent( TalkingEntity.class );
TalkingEntity e = getComponent( TalkingEntity.class );
This presumes all entities are not anonymous, but it reads clearer and removes the giant list of integer constants.

This is the best component oriented argument I've seen for game design, but it's based on the notion that inheritence doesn't solve all problems. I agree, but that doesn't mean component oriented structures do either. You can end up with more boiler plate code in the components themselves, and tight coupling between seperate component classes. It also doesn't bring anything new to the bulk of the problems in a game except potentially slightly more code. The component oriented structure only really solves specific corner cases (like a talking door), and I'd expect there are plenty of cases where inheritence can be far cleaner.

In regards to your example above you could add an 'Interaction' interface that all entities can hold. A sub-class could be a 'TalkingInteraction' which handles the talking code. You add your TalkingEntity to your TalkingDoor and to NPCs. You might also add a marker interface, Interactable, which the player looks for when searching for interactable objects.

My overall suggestion is that a mix of clean design patterns is better then one do all solution. In this case componetisation to handle player interactions and inheritence to handle everything else. Similar is done with some GUI libraries: inheritence for the objects (a custom button) and components for the actions (an action that is performed when clicking on your button). Neither inheritence or componetisation solves all problems.

I liked the diagrams and it's a good example.
« Last Edit: January 02, 2010, 05:42:18 pm by StudioFortress » Logged

Pages: [1] 2 3 4
Print
Jump to:  

Theme orange-lt created by panic