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?
Creating the Entity Class
OK, so let's port MonsterWars to my own personal variant of the Entity System approach.
First, download this starter project that is a "bare bones" version of MonsterWars. Build and run, and you'll see the following:
As you can see, this is a pretty bare bones project. It contains some code to do the basic UI setup (background, buttons, labels and such), but none of the gameplay logic is added yet.
Note: Just because you're using a component based approach doesn't mean every single thing needs to be inside components. You can if you want, but for this game it was easier to pull the background/UI/decoration stuff outside into the main game layer and just use the component based approach for the monsters/game objects.
Note: Just because you're using a component based approach doesn't mean every single thing needs to be inside components. You can if you want, but for this game it was easier to pull the background/UI/decoration stuff outside into the main game layer and just use the component based approach for the monsters/game objects.
Next, create a new group called EntitySystem, and a new group under that called Framework.
Then add a new file to the Framework group using the Objective-C class template. Name the new class Entity, and make it a subclass of NSObject.
Open Entity.h and replace the contents with the following:
@interface Entity : NSObject
- (id)initWithEid:(uint32_t)eid;
- (uint32_t)eid;
@end
And replace Entity.m with the following:
#import "Entity.h"
@implementation Entity {
uint32_t _eid;
}
- (id)initWithEid:(uint32_t)eid {
if ((self = [super init])) {
_eid = eid;
}
return self;
}
- (uint32_t)eid {
return _eid;
}
@end
Pretty simple, eh? As you can see, an Entity is literally just an integer ID - nothing else. It doesn't even have a list of components associated with it - that's done somewhere else.
In fact, you don't even really need an object for an Entity at all! You could just pass the integer ID around. However, I find it useful to have an object wrapper for the Entity for convenience - you'll see why later on in this tutorial.
Creating the Component Class and a Subclass
Next create a new group under EntitySystem called Components.
Then add a new file to the Components group using the Objective-C class template. Name the new class Component, and make it a subclass of NSObject.
And then you're done :] This is a completely empty class, all the interesting stuff will be in subclasses.
Again, this is a class that is optional and you don't even really need. I like having it for a bit of type safety though.
Let's create two subclass for now so you can see some examples. Add a new file to the Components group using the Objective-C class template. Name the new class RenderComponent, and make it a subclass of Component.
Replace the contents of RenderComponent.h with the following:
#import "Component.h"
#import "cocos2d.h"
@interface RenderComponent : Component
@property (strong) CCSprite * node;
- (id)initWithNode:(CCSprite *)node;
@end
And replace RenderComponent.m with the following:
#import "RenderComponent.h"
@implementation RenderComponent
- (id)initWithNode:(CCSprite *)node {
if ((self = [super init])) {
self.node = node;
}
return self;
}
@end
As you can see, this class is literally just data - along with a convenience method to initialize the data. According to the Entity System design, this class should have no code to actually operate upon the data - that is the job of the systems, which you'll develop later.
Let's see another example. Add a new file to the Components group using the Objective-C class template. Name the new class HealthComponent, and make it a subclass of Component.
Replace the contents of HealthComponent.h with the following:
#import "Component.h"
@interface HealthComponent : Component
@property (assign) float curHp;
@property (assign) float maxHp;
@property (assign) BOOL alive;
- (id)initWithCurHp:(float)curHp maxHp:(float)maxHp;
@end
And replace HealthComponent.m with the following:
#import "HealthComponent.h"
@implementation HealthComponent
- (id)initWithCurHp:(float)curHp maxHp:(float)maxHp {
if ((self = [super init])) {
self.curHp = curHp;
self.maxHp = maxHp;
self.alive = YES;
}
return self;
}
@end
Again, just data and a convenience method.
Creating the Entity Manager
Next you need to create an object that acts as the "database", where you can look up entities, get their list of components, and so on. In this game, you'll call this class the Entity Manager.
Add a new file to the Framework group using the Objective-C class template. Name the new class EntityManager, and make it a subclass of NSObject.
Open EntityManager.h and replace the contents with the following:
#import "Entity.h"
#import "Component.h"
@interface EntityManager : NSObject
- (uint32_t) generateNewEid;
- (Entity *)createEntity;
- (void)addComponent:(Component *)component toEntity:(Entity *)entity;
- (Component *)getComponentOfClass:(Class)class forEntity:(Entity *)entity;
- (void)removeEntity:(Entity *)entity;
- (NSArray *)getAllEntitiesPosessingComponentOfClass:(Class)class;
@end
And replace EntityManager.m with the following:
#import "EntityManager.h"
@implementation EntityManager {
NSMutableArray * _entities;
NSMutableDictionary * _componentsByClass;
uint32_t _lowestUnassignedEid;
}
- (id)init {
if ((self = [super init])) {
_entities = [NSMutableArray array];
_componentsByClass = [NSMutableDictionary dictionary];
_lowestUnassignedEid = 1;
}
return self;
}
@end
This just initializes the data structures you'll be using for the EntityManager. You'll have a list of all the entities (which remember are just integers!), then you'll have a dictionary that contains a list of each type of components (RenderComponents, HealthComponents, etc). Finally, you'll keep track of the lowest unassigned entity ID to use.
Next add this method to generate a new entity ID:
- (uint32_t) generateNewEid {
if (_lowestUnassignedEid < UINT32_MAX) {
return _lowestUnassignedEid++;
} else {
for (uint32_t i = 1; i < UINT32_MAX; ++i) {
if (![_entities containsObject:@(i)]) {
return i;
}
}
NSLog(@"ERROR: No available EIDs!");
return 0;
}
}
As you can see, it simply returns the next highest number - until it wraps around to the maximum integer, in which case it looks through the list of entities for an available entity ID.
Next add this method to create a new Entity:
- (Entity *)createEntity {
uint32_t eid = [self generateNewEid];
[_entities addObject:@(eid)];
return [[Entity alloc] initWithEid:eid];
}
This is as simple as generating a new entity ID and adding it to the list. It returns the wrapper Entity object for convenience.
Next add this method to add a component to an entity, and a method to get a component for an entity of a particular class:
- (void)addComponent:(Component *)component toEntity:(Entity *)entity {
NSMutableDictionary * components = _componentsByClass[NSStringFromClass([component class])];
if (!components) {
components = [NSMutableDictionary dictionary];
_componentsByClass[NSStringFromClass([component class])] = components;
}
components[@(entity.eid)] = component;
}
- (Component *)getComponentOfClass:(Class)class forEntity:(Entity *)entity {
return _componentsByClass[NSStringFromClass(class)][@(entity.eid)];
}
The addComponent method first looks inside the dictionary of components by class, where the key is the name of the class (in string version), and the value is a second dictionary. The second dictionary's key is the entity ID, and the value is the component itself. The getComponentOfClass simply reverses this lookup.
Note: With this approach, there can only be one instance of a component per entity. You may have a game where you want to have multiple instances of a component per entity (such as two guns) - if that's the case, you can change these data structures a bit so instead of the value of the second dictionary being a component, it would be a list of components instead.
Note: With this approach, there can only be one instance of a component per entity. You may have a game where you want to have multiple instances of a component per entity (such as two guns) - if that's the case, you can change these data structures a bit so instead of the value of the second dictionary being a component, it would be a list of components instead.
Next add this method to remove an entity:
- (void)removeEntity:(Entity *)entity {
for (NSMutableDictionary * components in _componentsByClass.allValues) {
if (components[@(entity.eid)]) {
[components removeObjectForKey:@(entity.eid)];
}
}
[_entities removeObject:@(entity.eid)];
}
This simply removes all components for an entity from the data structures, then removes the entity itself.
Finally, add this helper method:
- (NSArray *)getAllEntitiesPosessingComponentOfClass:(Class)class {
NSMutableDictionary * components = _componentsByClass[NSStringFromClass(class)];
if (components) {
NSMutableArray * retval = [NSMutableArray arrayWithCapacity:components.allKeys.count];
for (NSNumber * eid in components.allKeys) {
[retval addObject:[[Entity alloc] initWithEid:eid.integerValue]];
}
return retval;
} else {
return [NSArray array];
}
}
This is a helper method to find all the entities that have a particular component. Think of it as a "select * from component_name" query.
And that's it for the Entity Manager for now. There's only a few more steps before you're ready to try this out!