Making GameplayKit Swift-ier

I've posted before on the hot-mess that is GameplayKit. I've since been playing with it a lot, particularly with the entity/component system. I think there are some real problems with the implementation that I suspect may be part of trying to cater to Objective-C and Swift. In this article we'll take a look at those issues, and a Swift re-implementation of the GameplayKit API that solves these problems. 

What's the Big Idea?

GameplayKit's GKEntity, GKComponent, and GKComponentSystem are intended two achieve two goals

  1. Provide rendering engine independence : If you build your game engine around entities and components you could (theoretically at least) make it completely independent of any particular rendering engine, providing some mapping of entities into, for example, SpriteKit in a given implementation. 
  2. Encourage Composition: Rather than leverage inheritance (with potentially hairy class trees) its better to compose your entities (or other game systems) with a number of common components. For example you might add a "Shield" component to everything that can have shields.

These are great goals, you might want to start out with SpriteKit, but have a vision of going straight to Metal later, or perhaps SceneKit. So if we wanted to abstract out position and rotation (here I'm going to keep it 2D, but you would really make them 3D points and orientations ready for 3D when you get to it). 

class Position : GKComponent{
    var point = CGPointZero
    
    override var description : String{
        return "Position {x \(point.x), y \(point.y)}"
    }
}

class Orientation : GKComponent{
    var zRotation : CGFloat = 0.0
    
    override var description : String{
        return "Orientation {z \(zRotation)}"
    }
}

let entityA = GKEntity()
entityA.addComponent(Position())
entityA.addComponent(Orientation())


let entityB = GKEntity()
entityB.addComponent(Position())
entityB.addComponent(Orientation())

entityA.componentForClass(Position)
entityB.componentForClass(Orientation)

Here we define two components, one for position and one for orientation, and add them to an entity. We get can get either by calling componentForClass on the entity. This is all good. However, there is a problem. If I am using this to introduce a layer of abstraction I'm going to have to implement these in any given rendering engine by making it pull the components from the entity in an update cycle and map it to the objects in the rendering engine. Before long you will have added code to make sure you need to do it in any given frame. All of a sudden you are adding a pile of complexity and slow-ness to the game loop. Bad thing. 

What you would really want to do is provide a sub-class that is directly tied to the rendering object (say the SKNode). Something like this

class SpriteKitPosition : Position{
    var skNode : SKNode
    
    init(skNode : SKNode){
        self.skNode = skNode
    }
    
    override var point : CGPoint {
        get{
            return skNode.position
        }
        set{
            skNode.position = newValue
        }
    }
}

Easy right? Not quite, if you were to do that then anything in your game  engine that tries to get the position, without wanting to care how it is actually implemented might try the following

entityA.componentForClass(Position)

It won't work. You get nil because SpriteKitPosition is not EXACTLY position. This isn't just a rendering engine independence issue either (just incase you were thinking "I don't care, it's SpriteKit for ever for me!"). You get the same thing if you want to abstract controllers. 

I would like any entity that could be controller by a keyboard, touch-screen, dedicated gaming controller OR an AI controller to just have a single controller component. That way I can have my entity (or even a component of my entity) grab that controller component and turn that into motion in whatever way is appropriate. As long as the particular controller component implementation fills in the controller data in correctly, the rest of the engine can ignore HOW that controller data was captured or created. Let's enrich our entity component model with some new components and add a ComponentSystem that will update all the controller components at the same time (e.g. before the main game loop runs)

class Controller : GKComponent{
    override var description : String{
        return "Controller"
    }
}

class AIController : Controller{
    override var description : String{
        return "AI-Controller"
    }
}

class KeyboardController : Controller{
    override var description : String{
        return "Keyboard-Controller"
    }
}

entityA.addComponent(AIController())
entityB.addComponent(KeyboardController())

let controllers = GKComponentSystem(componentClass: Controller.self)

controllers.addComponentWithEntity(entityA)
controllers.addComponentWithEntity(entityB)

let entityAController : Controller? = entityA.componentForClass(Controller)
let entityBController : Controller? = entityB.componentForClass(Controller)

We hit the same issue... entityAController and entityBController will be nil and our controllers GKComponentSystem will contain nothing... even though both are Controllers. 

Being a little more Swift-y

Firstly, let's set everything up with Protocols. Even ignoring any Swift 2.0 cleverness this enables an important capability; you can provide your own implementation of anything and it can integrate with the rest of the standard system. Remember my enthusiasm for performance being in-check? Well now I could provide an implement of the entity protocol that has all the rendering engine information built in directly. The rest of the entity/component system can still be ignorant of it, but we can remove the thunks. 

Here's the full set of protocol definitions to make this work

// We will havea  number of things that can be updated, so let's break that out into a protocol
protocol SGKUpdateableType{
    func updateWithDeltaTime(seconds:NSTimeInterval)
}

//Components have only two requirements, one that they can be updated and the second that they can
//compare themselves to other components
protocol SGKComponentType : SGKUpdateableType{
    //    var  functionality : ComponentFunctionality {get}
    var  entity : SGKEntityType? {get}
    func isEqualTo(other:SGKComponentType)->Bool
}


//If a specific implementation of a component implements equatable then we can have a default implementation
//of isEqualTo that uses that.
extension SGKComponentType where Self : Equatable{
    func isEqualTo(other: SGKComponentType) -> Bool {
        if let o = other as? Self { return self == o }
        return false
    }
}

//
// This is a standalone protocol. A default implementation is provided BUT you are able to make anything implement the
// protocol. This means that you do not have to build multiple levels of abstraction to integrate the entity component
// system with your actual game rendering engine
//
protocol SGKEntityType : SGKUpdateableType{
    var         components : [SGKComponentType] {get}
    func        addComponent(component:SGKComponentType)
    func        getComponent<ComponentType:SGKComponentType>()->ComponentType?
    func        removeComponent(component: SGKComponentType)
}

// Our component System that this time can take advantage of generics
// for type checking etc. 
protocol SGKComponentSystemType : SGKUpdateableType{
    typealias   ComponentType
    
    var components : [ComponentType] {get}
    
    func addComponent(component:ComponentType)
    func removeComponent(component:ComponentType)
    
    func addComponentWithEntity(entity:SGKEntityType)
    func removeComponentWithEntity(entity:SGKEntityType)
}

Note here we are using the Swift 2.0 pattern to provide a default implementation of the equalTo method for any implementation of SGKComponent that is already equitable.

We can also make our component system use generics rather than have a constructor that takes a class, this is much better as we then get all of the type checking support the compiler can offer. 

Finally, getComponent can fully leverage all of the new Swift generic capabilities to automatically determine the desired Component class rather than needing it to be passed as a parameter, so this has changed from getComponentForClass to just getComponent

That's it. Our implementation wouldn't change much. 

class Position : SGKComponent, CustomStringConvertible{
    var point = CGPointZero
    
    var description : String{
        return "Position {x \(point.x), y \(point.y)}"
    }
}

class Orientation : SGKComponent, CustomStringConvertible{
    var zRotation : CGFloat = 0.0
    
    var description : String{
        return "Orientation {z \(zRotation)}"
    }
}

let entityA = SGKEntity()
entityA.addComponent(Position())
entityA.addComponent(Orientation())

let entityB = SGKEntity()
entityB.addComponent(Position())
entityB.addComponent(Orientation())

Everything will work as it did before. Let's explore our component system... it now makes much more sense and behaves in the way you would expect. 

class Controller : SGKComponent, CustomStringConvertible{
    var description : String{
        return "Controller"
    }
}

class AIController : Controller{
    override var description : String{
        return "AI-Controller"
    }
}

class KeyboardController : Controller{
    override var description : String{
        return "Keyboard-Controller"
    }
}

entityA.addComponent(AIController())
entityB.addComponent(KeyboardController())

let controllers = SGKComponentSystem<Controller>()

controllers.addComponentWithEntity(entityA)
controllers.addComponentWithEntity(entityB)

//These will work fine now
let entityAController : Controller? = entityA.getComponent()
let entityBController : Controller? = entityB.getComponent()

//Just to make sure it all works as we would expect, could we get all the AIController components... the one that has the AIController will get it, that one that doesn't, doesn't. 
let entityAAIController : AIController? = entityA.getComponent()
let entityBAIController : AIController? = entityB.getComponent()

So What?

I haven't spent much time tracking what you can and can't do with generics and protocols in Objective-C in this year's updates because I no longer work in Objective-C, and I'm sure much of this could be have been achieved in Objective-C... or perhaps better, or perhaps in a more Objective-C way... I'm not interested in that bun fight. My feeling remains at the end of this that this very useful API is weakened by seeming to support last year's capabilities. It also, in its design, falls short of its objectives. 

This particular corner of GameplayKit is easy to build a different implementation for, but it is the most pervasive and the most important one to get right (in my opinion as it applies to ALL games, rather than other elements of the SDK which apply only to certain types of games). 

I've included the full implementation of the protocols and examples given above in a playground, feel free to download and play!