GameplayKit: How to implement Adam Martin’s Entity Component System

GameplayKit’s Entity Component System (ECS) has been criticised for being a “disappointing mess” and “not an ECS”.

I wouldn’t go nearly this far to say the least, but fact remains: Adam Martin’s variant of ECS is more prevalent in AA(A) studios and their engines. In Martin’s ECS, components only contain data and no logic at all.

Whereas GameplayKit’s ECS is an implementation of the classic Scott Bilas’ ECS in which components contain both data and logic. It was derived from the Component-Based Development principles.

Entity Component System Variants (Bilas, Martin, GameplayKit) with pseudo-code examples
Entity Component System Variants (Bilas, Martin, GameplayKit) with pseudo-code examples

Entity Component System Variants

If you care about the various entity component system differences you should read this gist that I put together to show architectural differences with pseudo-code examples. Overall, differences are minor and the article explains why it can still be important to prefer Martin’s ECS over the others.

This article is about implementing Martin’s ECS perusing GameplayKit’s existing ECS classes. Of course you could also roll your own Martin’s ECS from scratch.

1. Subclass GKComponentSystem

Each subclass of GKComponentSystem will be a component system subclass that runs the core logic for the related components.

So instead of calling each component’s updateWithDeltaTime: method via the GKComponentSystem‘s base implementation of updateWithDeltaTime: you instead override updateWithDeltaTime: in your component system subclass.

Then just enumerate all components in the system to run whatever logic you need on the components’ data.

2. Subclass GKComponent

To be able to use the GKComponentSystem‘s existing interface all components also need to be subclasses of GKComponent. This can only be avoided if you roll out your own Martin’s ECS implementation. Here we take the easy and sufficient way out and just ignore any of the logic that already exists in the GKComponent class.

The @interface defines all necessary properties (data). Optionally you can make the synthesized property ivars public or use public ivars to begin with if you want to save just a few extra CPU cycles (ObjC messaging overhead).

It’s important to note that in Martin’s ECS the components must not contain any logic! Since it’s still possible to add such a component to the wrong component system, or incorrectly implementing the component system, it’s a good idea to override the component’s updateWithDeltaTime: method to unconditionally raise an error when it does get called.

3. Add Components to System

This can be done as usual with the existing GKComponentSystem functionality. You first create an instance of your component system, then add components to it.

When instancing components you should make sure that you use good or at least safe default values. If those defaults are pretty constant you should set them in the init method of your component subclass to avoid repeating this for every instantiation.

You could also implement a common base class for your Martin’s components, but still inherit from GKComponent. This base class could add functionality to read data from disk, a database or a URL and then calls a “load” method that your subclasses implement to read whatever data they need from the file or stream. Or use KVC or runtime reflection to fill in the data automatically.

4. Update Component Systems

To set things in motion, just call updateWithDeltaTime: of your component systems. For instance from the scene’s update method.

There you go: a working Martin’s ECS implemented using GameplayKit’s ECS (Scott Bilas variant).

What about built-in components?

There’s a minor caveat with this solution: any built-in component such as GKAgent2D will need to run their updateWithDeltaTime: method in the implementation above. That’s because their logic is contained within the component class. However, this shouldn’t be a problem.

Of course the above code fragment is rather unnecessary.

Instead you should just use an instance of the base class GKComponentSystem for all built-in components. Initialize the component system with the [GKAgent2D class] and call the system’s updateWithDeltaTime: method as in step 4. The GKComponentSystem will dispatch (forward) the updateWithDeltaTime: message to its components automatically.

So there’s no need to do it like in the code fragment above, unless .. you want to perform additional logic on each agent before and after updating it. For instance, you might want to perform the code normally put in agentWillUpdate: and agentDidUpdate: steps right in the loop rather than using the GKAgentDelegate protocol.

Final Words

There you go. It seems almost too good to be true, right?

Here’s a little secret: I haven’t tested any of this. So there may be compile errors and a chance this might fail for other reasons – please let me know if it does. Stil, I’m pretty confident that I’ve got it right.

More GameplayKit goodies will be in the GameplayKitExtensions.framework that I’m building while integrating GameplayKit in TKTilemapKit. More about that soon.

Comments

Loading Facebook Comments ...
One response... add one

Leave a Reply

Your email address will not be published. Required fields are marked *