June 25, 2014
SpriteKit and Entity-Component-Systems
In the Swift engine, I want to use Apple’s SpriteKit for rendering and physics to decrease development time and increase engine efficiency. Unfortunately, SpriteKit is not particularly well-suited to the ECS architecture. I had to come up with a solution that would allow me to leverage SpriteKit without breaking the ECS paradigm in the process.
The basic building block for SpriteKit scenes is the SKNode and its subclasses. The base class alone contains functionality for position, rotation, transparency, user interaction, hierarchy, actions, physics, and more. The subclasses add even more. The good news is that Apple’s framework is powerful and flexible. The bad news is that it is monolithic, which is antithetical to the modular ECS approach.
My first instinct was to wrap SKNode in a component so that I could attach it to an entity. The problem is that this eliminates one of the benefits of the ECS approach. In ECS, we can determine what an entity does based on what components are attached to it. Systems can have lists of entities possessing specific combinations of components, such as a collision system that processes all entities that have a physics body and a position. By attaching everything in a single component as a node, we can no longer tell anything based on which components are attached.
The solution I’ve come up with is to attach the node as a component, then create components that can map their properties to the node’s properties. The position component’s x and y values, for example, can be mapped by the position system to the corresponding values in the SKNode. Here’s how the position component looks in Swift:
class LGPosition: LGComponent
{
// [LGComponent protocol functions omitted]
// node is where mapping occurs
var node: SKNode!
// Stored properties if there is no mapping
var _x: Double
var _y: Double
// Computed properties either map to the node or use the stored properties
var x: Double
{
get { if node { return Double(node.position.x) } else { return _x } }
set { if node { node.position.x = CGFloat(newValue) } else { _x = newValue } }
}
var y: Double
{
get { if node { return Double(node.position.y) } else { return _y } }
set { if node { node.position.y = CGFloat(newValue) } else { _y = newValue } }
}
// [initializer omitted]
}
The computed properties, x and y, are where the magic happens. If an SKNode has been attached to this component, the getters return the corresponding values from the node and the setters set the corresponding values in the node. If no node has been attached, the getters and setters use the stored _x and _y properties instead. That way, any entity can have a position, but entities that are using SpriteKit will automatically update SKNode.position. Here’s how the position system attaches the position component to the SKNode:
class LGPositionSystem: LGSystem
{
// [initializer omitted]
override func accepts(entity: LGEntity) -> Bool
{
// LGNode simply wraps an SKNode
return entity.has( LGPosition.type(), LGNode.type() )
}
override func add(entity: LGEntity)
{
super.add(entity)
let position = entity.get( LGPosition.type() ) as LGPosition
let node = entity.get( LGNode.type() ) as LGNode
// Set the SKNode's position to the LGPosition's position (in case it had already been set)
node.sknode.position = CGPointMake(CGFloat(position.x), CGFloat(position.y))
// Attach the SKNode to the LGPosition component
position.node = node.sknode
}
}
Position works with or without SpriteKit, but some components will not work without it (such as the physics body component, which requires an instance of SKPhysicsBody to be explicitly passed in the initializer). Building the entity looks like this in the game scene:
let player = LGEntity() player.put( LGPosition(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame)), LGSprite(spriteSheet: playerSpriteSheet), LGNode(sprite: true), LGPhysicsBody(skphysicsbody: playerPhysicsBody) )
I’ve decomposed the SKNode into position, physics body, and sprite already. Be sure to take a look at the repository. This approach seems to be the best way to integrate SpriteKit with the ECS architecture, but I am not sure that I like having to explicitly attach an SKNode or SKSpriteNode to every entity that needs to use SpriteKit. I might explore the possibility of creating systems that automatically add the appropriate SpriteKit node to an entity if the entity has components that require SpriteKit.
My main focus at the moment is getting tile maps back into the engine, but I’m doing a lot of refactoring and experimenting with different approaches as I go. Swift is still a beta programming language and has a number of bugs and quirks that will hopefully be smoothed out over the next few weeks, and the engine will evolve and change along with it.
Have you considered decomposing the different conceptual “components” of SKSpriteNode into protocols and then using extensions to make SKSpriteNode conform to those protocols? (See “Adding Protocol Conformance with an Extension” in the Swift guide.) It’s not clear to me how you’re constructing entities/components, but assuming you have some sort of factory or template processor, I think it’d be fair to let that factory (or possibly some of the templates themselves?) know you’re using SpriteKit and construct an SKSpriteNode for “those” components.
It would mean rethinking how you store/access components (because you’d have one component object acting as multiple component types), but it should make for fairly clean code because once the SKSpriteNode’s constructed most of the systems don’t need to know it’s there — they think they’re just looking at a collection of components.
That’s an intriguing idea! It would eliminate the need to explicitly add the
SKSpriteNodeas a component and the need for systems to bind the node to other components. The entities that use SpriteKit would also not need to have unused stored properties (although that might also be avoided by using lazy properties).I have two primary concerns with that approach, though. One is that those protocols would not be available as stand-alone components to attach to non-SpriteKit entities. I could implement classes that conform to that protocol and use those for non-SpriteKit entities, but that doesn’t feel tidy to me, having two ways to add the same functionality to an entity.
My other concern is that it alters the way you would construct an entity, because an entity factory would have to have some kind of prior knowledge about how the engine works. The engine has no built-in factory, since that’s more of a game-specific thing, and I would rather have a clean and uncluttered interface to the engine one the game development side of things — almost so that a developer would be able to treat the engine as a black box.
I’m not convinced that my current approach is the best, but it seems to do a good job of keeping the interface clean when developing a game on the engine. I appreciate your thoughts, and I’ll definitely be thinking about alternative approaches like that.
That’s a fair point. I suspect eventually though, either some engine-dependent pieces are going to creep into entity construction, or you’re going to have to add another abstraction layer that entity construction can call into, if only because there are going to be implementation-dependent details around things like images, sounds, particle effects. I could be wrong, though!
Anyway, awesome project, and I’m going to keep watching.
Pingback: SpriteKit and Entity-Component-Systems Revisited | Dev Blog