Using Swift GameplayKit to achieve rendering framework independence

In the last article we looked at providing a better (and purely Swift) implementation of GameplayKit's Entity/Component architecture. By better I didn't mean "it's Swift so it's better"... I meant better at solving the stated objectives of GameplayKit: Rendering framework independence and composition. I focused on composition last time, this time I look at how the Swift GameplayKit implementation (and Swift itself) makes rendering framework independence easy. 

Scroll to the bottom to get the updated playground if you just want to dive in!

 

Making Promises

Keeping our Swift style approach, we might define some new SGKComponent types for things that can be positioned, and have their alpha values adjusted. We achieve this by creating two new protocols.

SGKPositionable

I have been a little cheeky here to keep the example simple, and just mirrored the position attributes of a SpriteKit SKNode. If we were to extend with a SceneKit implementation we would have map these to the SceneKit position property.

public protocol SGKPositionable : SGKComponentType{
    var     position    : CGPoint {get set}
    var     zPosition   : CGFloat {get set}
}

SGKFadeable

We just promise a single alpha value here as well, and again it exactly matches the SpriteKit SKNode implementation for simplicity of the example.

public protocol SGKFadeable : SGKComponentType{
    var     alpha       : CGFloat {get set}
}

SpriteKit Implementation

Now let's take a look at a concrete implemenation using SpriteKit. We will take SKNode and extend it to conform to both protocols, meaning we can add it to an SGKEntity. That entity will now just be able to require that it has a SGKPositionable component, but not care how that promise if fullfilled.

The implementation is very simple. Note that SGKExternallyUpdatedComponent is a protocol that is used to provide a default implementation of update (that does nothing) in cases where the component is really updated by an external mechanism.

SKNode's can be both Positionable and Fadeable, so we will implement both interfaces in a single "Component". This isn't possible with the standard GameplayKit implementation as you query based on a single class value that must exactly match. SwiftGameplayKit just checks for conformance.

We also make use of SKNode's userData property to enable the component to implement the promise of storing the entity.

private let SKNodeEntityKey = "SGKEntityKey"
extension SKNode : SGKPositionable, SGKFadeable, SGKExternallyUpdatedComponent{
    public var entity : SGKEntityType? {
        get{
            return userData?[SKNodeEntityKey] as? SGKEntityType
        }
        set{
            if let userData = userData{
                userData[SKNodeEntityKey] = newValue as? AnyObject
            } else if let entity = newValue as? AnyObject{
                userData = NSMutableDictionary(objects: [entity], forKeys: [SKNodeEntityKey])
            }
        }
    }
}

Using the implementation

We can now create an entity, create a SKNode (in this case a SKSpriteNode), and add it directly to the entity.

let entityA = SGKEntity()
let mySprite = SKSpriteNode()
entityA.addComponent(mySprite)

Now if we have code that depends on the entity being able to fullfil the promise of being positionable and fadeable we can simply get the components that implement those individual protocols.

if var positionable : SGKPositionable = entityA.getComponent(), var fadeable : SGKFadeable = entityA.getComponent(){
    //Check setting the position on the component actually changes the node
    "Should be 10,10 for both"
    positionable.position = CGPoint(x: 10, y: 10)
    mySprite.position
    //Check setting the alpha on the component actually changes the node
    "Should be 0.5 for both"
    fadeable.alpha = 0.5
    mySprite.alpha
}

Consolidating Requirements

The above approach is fine, but we will probably want ANY rendering engine we use to render graphics to conform to a set, rather than just one, of the protocols. If everytime we want to get those components we have to pull out variables for each specfic protocol that's going to be a pain.

A nice solution is to consolidate all of the requirements of a given game into a single protocol. It makes it explicit what you expect and if the game engine doesn't support it, you will know at compile time and can make a decision about how to approach the problem.

So we define a protocol that inherits from all of the other protocols we need to be supported...

protocol MyGameRenderable : SGKPositionable, SGKFadeable{}

And then extend SKNode (in this case) to support it

extension SKNode : MyGameRenderable{}

Now we can simplify the whole process, knowing that an entity does (or does not if we guard) provide all of the required features. Again, at compile time.

if var renderable : MyGameRenderable = entityA.getComponent(){
    renderable.position
    renderable.alpha
}

Updated Playground

I've also updated the playground, get the latest version here.