Let's build GameplayKit - Components

26 Jun 2015

I dig Apple’s new GameplayKit. It’s nothing revolutionary, just a collection of solid utilities for things commonly needed while making games. I want to use it now, but for various reasons I can’t run a beta OS or IDE on a regular basis.

Since I want it now and because I’m curious about how it’s put together, I’m going to implement at least some of it from scratch using the preliminary documentation and videos.

They’re straightforward, and necessary for some of the later parts, so I’m starting with the Entity Component System. If you’re not familiar with Entity Component Systems, I suggest watching the GameplayKit WWDC 2015 talk, or for a deeper look, read the Components chapter in Game Programming Patterns.

The full source code for this series of posts is up as JLFGameplayKit on GitHub. The code for this particular post is tagged as part1-components.

So let’s begin! At a really high level, an Entity is nothing but a collection of components, where components are little bundles of state and logic that when put together make an object in your game.

GameplayKit’s GKEntity class looks like this:

@interface GKEntity : NSObject

+ (instancetype)entity;
- (instancetype)init;

@property (nonatomic, readonly, retain) NSArray *components;

- (GKComponent *)componentForClass:(Class)componentClass;
- (void)addComponent:(GKComponent *)component;
- (void)removeComponentForClass:(Class)componentClass;

- (void)updateWithDeltaTime:(NSTimeInterval)seconds;

@end

Based on that definition and the GKEntity documentation, we know a couple key things:

The most straightforward way to implement that is to use a dictionary. We can’t use NSMutableDictionary directly though: its keys must be objects that implement NSCopying, and Class instances are just C structs. So we’ll turn to NSMapTable. NSMapTable lets us use arbitrary pointers as table keys, so we’re good to go.

Our implementation of GKEntity (which I’m creatively calling JLFGKEntity) looks like this:

@interface JLFGKEntity ()

@property (nonatomic, strong) NSMapTable *componentMap;

@end

@implementation JLFGKEntity

- (instancetype)init
{
    self = [super init];
    if (self != nil) {
        _componentMap = [NSMapTable mapTableWithKeyOptions:NSMapTableObjectPointerPersonality
                                              valueOptions:NSMapTableStrongMemory];
    }
    return self;
}

/* ... */
@end

The methods of the class just add and remove things from self.componentMap with a little bit of sanity checking, and the components property just returns an NSArray of the values in the component map. Easy peasy.

Alright then, components themselves. There’s very little to them:

@interface GKComponent : NSObject

@property (nonatomic, readonly, weak) GKEntity *entity;

- (void)updateWithDeltaTime:(NSTimeInterval)seconds;

@end

The only tricky thing there is that entity property being read-only. We’ll solve that by adding a private category:

@interface JLFGKComponent (Private)
@property (nonatomic, readwrite, weak) JLFGKEntity *entity;
@end

And our entity class can use that to set that property when a component is added or removed.

Last up are component systems. These aren’t mandatory to use: they give you a way to strictly control the order in which components get updated. Instead of calling GKEntity’s -updateWithDeltaTime: method, you add components to GKComponentSystem instances and the update method there.

GKComponentSystem’s interface looks like this:

@interface GKComponentSystem : NSObject

- (instancetype)initWithComponentClass:(Class)componentClass;

@property (nonatomic, readonly) Class componentClass;
@property (nonatomic, readonly, retain) NSArray *components;

- (void)addComponent:(GKComponent *)component;
- (void)addComponentWithEntity:(GKEntity *)entity;
- (void)removeComponent:(GKComponent *)component;
- (void)removeComponentWithEntity:(GKEntity *)entity;

- (void)updateWithDeltaTime:(NSTimeInterval)seconds;

- (GKComponent *)objectAtIndexedSubscript:(NSUInteger)idx;

@end

Things to take away from this:

Specifically: -objectAtIndexedSubscript: means that the components in the component system are ordered, probably in the order they were added to it. Were I writing this from scratch, I’d probably have used a set instead of an array; it’d help avoid adding the same component twice. Sets aren’t ordered though, so we’re stuck using an NSMutableArray to back this. Once that’s determined, the other methods just add or remove things from that array and forward the -updateWithDeltaTime: method to the components.

So that’s all well and good, but how does it help? I had trouble coming up with a minimal demo for this, so it’s a little big, but if you take a look at the source:

The three types of entities in the scene are built up like so:

So no specific entity type classes at all, just entities built up from components. Give it a look!

JFLGameplayKit on GitHub

Next up, I’ll tackle either state machines or pathfinding.