Let's build GameplayKit - Components
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:
Based on that definition and the GKEntity documentation, we know a couple key things:
- Components are indexed by their type.
- There can only be one component of a given type associated with the entity at a time.
- I don’t see any guarantee about what order components will be updated in.
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:
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:
The only tricky thing there is that entity
property being read-only. We’ll solve that by adding a private category:
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:
Things to take away from this:
- Again, only one kind of component class is accepted by each component system.
- The
-add/removeComponentWithEntity
methods are just convenience wrappers for-add/removeComponent
. - The
-objectAtIndexedSubscript:
method puts a constraint on how we implement this one.
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:
- We have 3 “types” of entities: a player character, animated characters who wander around, and bouncy characters who wander around.
- That said, there are no entity specific classes: they’re all just JLFGKEntities with a different bundle of components.
SpriteComponent
simply holds onto anSKSpriteNode
that gives the entities presence in the scene. The update method on that one just sets zPosition based on the sprite’s y coordinate in the scene so that things sort properly.MoveComponent
handles moving the entities from point A to point B.WanderComponent
implements a simple state machine that waits for a bit, walks in a random direction for a bit, waits, walks, repeat. It tells the entity’sMoveComponent
where to go.BouncyAnimationComponent
animates a sprite by making it look like it’s bouncing along the way. It uses the direction from the entity’sMoveComponent
to figure out which way the sprite should be facing and sets theSpriteComponent
’s texture appropriately.CharacterAnimationComponent
animates a sprite by cycling through textures, normal frame based animation. It also uses the move and sprite components.
The three types of entities in the scene are built up like so:
- The player character has Sprite, Move, and Character Animation components. Player input is passed along to the Move component to tell it where to go. Just click in the scene and the player will follow the mouse around.
- There are a few other characters built up with Sprite, Move, Wander, and Character Animation components. On these ones, the Wander component is telling it where to go, but otherwise they work like the player does.
- Then there are a couple bouncy characters, using Sprite, Move, Wander, and Bouncy Animation. They work the same way as the second entity type, just with a different sort of animation.
So no specific entity type classes at all, just entities built up from components. Give it a look!
Next up, I’ll tackle either state machines or pathfinding.