Introduction to Component Based Architecture in Games
This is a blog post by site administrator Ray Wenderlich, an independent software developer and gamer. When you’re making a game, you need to create objects to represent the entities in your games – like monsters, the player, bullets, and so on. When you first get started, you might think the most logical thing is […] By Ray Wenderlich.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Introduction to Component Based Architecture in Games
55 mins
- Introducing MonsterWars
- A Shooting Castle: Overview
- A Shooting Castle: Implementation
- Drawbacks of Object Oriented Game Architecture
- Introduction to Component Based Architecture
- Component Based Architecture Approaches
- Object for Each Component with Message Passing
- Entity System Approach
- Creating the Entity Class
- Creating the Component Class and a Subclass
- Creating the Entity Manager
- Creating the Systems
- Putting it All Together
- Entity Factory
- Moving Monsters
- Where To Go From Here?
Drawbacks of Object Oriented Game Architecture
Even though this game is fairly simple, you can already see a number of problems with object oriented game architecture as the game gets more complicated:
- As you extend the game design, you start having more and more “exceptions” to the original rules. This makes you want to move more and more code to the base GameObject class, making it long and convoluted.
- Alternatively you might have a hierarchy that doesn’t make logical sense – i.e. is a Laser really a Monster?
- Code for various “systems” for the game is all jumbled together. Wouldnt it be better if all of the code related to shooting was in one place, and all the code for movement in another place?
These sorts of problems is exactly what component based game architecture is meant to solve! Let’s take a look.
Introduction to Component Based Architecture
The basic idea behind component based architecture is to prefer composition over inheritance.
This is a fancy way of saying “instead of inheriting from an object to reuse code/get functionality, build your game objects out of sub-components instead.”
Still confused? Here’s a diagram of an alternative way to organize two examples of objects from this game: the AI Player and the Zap Monster:
In this diagram, you see that both of these objects are instances of the same class – GameObject. However, these GameObjects don’t do much – they are just collection of components that do all the work.
So when adding an object to your game, you just add all the components that the object you’re trying to add needs. In this example:
- The AI Player needs to be rendered to the screen (as a castle), with a health bar, associated with a team, has properties as a player (such as coins), has a gun to shoot, some artificial intelligence.
- The Zap Monster needs to be rendered to the screen (as a zap monser), with a health bar, associated with a team, with a gun to shoot, and the ability to move.
The nice thing about organizing things about components like this is it makes it a lot easier to tweak your game design. Want your castle to start moving around? Just add a move component!
Note: Component based architecture also helps you do some quite fancy things. Instead of hard coding what components are in each game object, you could read a definition from an external file instead. This makes it really easy to create different behavior for your game objects without having to code, which is great for designers and quickly tweaking/iterating your game design.
Note: Component based architecture also helps you do some quite fancy things. Instead of hard coding what components are in each game object, you could read a definition from an external file instead. This makes it really easy to create different behavior for your game objects without having to code, which is great for designers and quickly tweaking/iterating your game design.
That’s the basic idea behind component based architecture. However, the trickiest thing about component based architecture are there are about a zillion ways to implement it.
Each programmer/studio seems to have their own preference and what works best for them. This makes trying to learn how to do it difficult because there are so many different approaches discussed out there.
So let’s take a look at some popular approaches, then you will pick one of them and convert this MonsterWars game to a component based architecture!
Component Based Architecture Approaches
As I mentioned, there is a wide variety of approches to component based architecture out there. Here are a few:
Simplest Method: Massive GameObject with On/Off Switches
In this method, you create a massive GameObject class (or structure) with every property you might need. For each “group” or “component” of properties, you add an on/off boolean to say whether that “component” is there. Here’s what it might look like for MonsterWars:
@interface GameObject : NSObject
// Render component
@property (assign) BOOL hasRender;
@property (strong) CCSprite * sprite;
// Health component
@property (assign) BOOL hasHealth;
@property (assign) float curHp;
@property (assign) float maxHp;
@property (assign) BOOL alive;
// Team component
@property (assign) BOOL hasTeam;
@property (assign) int team;
@property (assign) BOOL attacking;
// Gun component
@property (assign) BOOL hasGun;
@property (assign) float rangedRange;
@property (assign) float rangedDamage;
@property (assign) float rangedDamageRate;
@property (assign) float rangedLastDamageTime;
@property (strong) NSString * rangedSound;
// Move component
@property (asisgn) BOOL hasMove;
@property (assign) CGPoint velocity;
@property (assign) CGPoint acceleration;
@property (assign) float maxVelocity;
@property (assign) float maxAcceleration;
// Damage component
@property (assign) BOOL hasDamage;
@property (assign) float meleeDamage;
@property (assign) BOOL meleeDestroySelf;
@property (assign) float meleeDamageRate;
@property (assign) float meleeLastDamageTime;
@property (assign) BOOL meleeAoe;
@property (strong) NSString * meleeSound;
// Player Component
@property (assign) BOOL hasPlayer;
@property (assign) int coins;
@property (assign) double lastCoinDrop;
// AI Component
@property (assign) BOOL hasAI;
@property (strong) AIState * currentSTate;
// Human Component
@property (assign) BOOL hasHuman;
@end
Then inside your GameObject’s update method, you’d check if each component was there, and process it if so:
- (void)update:(float)dt {
if (self.hasRender) {
[self updateRender:dt];
}
if (self.hasHealth) {
[self updateHealth:dt];
}
if (self.hasTeam) {
[self updateTeam:dt];
}
if (self.hasGun) {
[self updateGun:dt];
}
if (self.hasMove) {
[self updateMove:dt];
}
if (self.hasDamage) {
[self updateDamage:dt];
}
if (self.hasPlayer) {
[self updatePlayer:dt];
}
if (self.hasAI) {
[self updateAI:dt];
}
if (self.hasHuman) {
[self updateHuman:dt];
}
}
This method was used by Phil Hassey in the development of his game Dynamite Jack (albeit a C/C++ based version). You can read more about it in his blog post on the matter.
If you think about it, this approach is not too far away from taking the Object-Oriented example above to the extreme end – just moving up all the code to the highest level GameObject, and just turning various features on and off.
My personal opinion about this approach:
- Pros: Definitely the simplest option, easy to understand and work with.
- Cons: Doesn’t have as much flexibility as some of the other approaches, code might have more of a tendency to get bloated for larger games.
Update: Adam Martin pointed out another con about this approach – it’s much more difficult to delete code with this approach than in some of the other techniques described below. For example, in the Entity System approach you can delete a system or add a new component and everything just works – no errors or dependencies involved! This is great for quick and dirty prototyping. On the other hand, this approach makes things a bit trickier, especially when you have old incompatible serialized game states lying around or if you want to make changes at runtime.
Update: Adam Martin pointed out another con about this approach – it’s much more difficult to delete code with this approach than in some of the other techniques described below. For example, in the Entity System approach you can delete a system or add a new component and everything just works – no errors or dependencies involved! This is great for quick and dirty prototyping. On the other hand, this approach makes things a bit trickier, especially when you have old incompatible serialized game states lying around or if you want to make changes at runtime.