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 StudySetting up an Inheritance TreeLet'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 ProblemThen, 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 SolutionIf 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.
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 ArchitectureThis 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 ComponentsFirst, 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):
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:
//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:
// 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;
}
//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:
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 EntityNow, 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 Rock1. ScalabilityWhenever 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-usabilityGames 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. FlexibilityThe 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. ConsistencyWhen 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 Not1.
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 TradeoffsAh, 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.