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.

Leave a rating/review
Save for later
Share
You are currently viewing page 4 of 7 of this article. Click here to view the first page.

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:

Empty starter project

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!

Contributors

Over 300 content creators. Join our team.