July 1, 2014

SpriteKit and Entity-Component-Systems Revisited

In my last post, I discussed how I made SpriteKit work with the Entity-Component-System approach to game development. I was not particularly pleased with my solution, so I have revisited it and come up with a slightly better way to integrate SpriteKit.

My original approach was to create several component classes corresponding to various theoretical components of SpriteKit’s SKNode, then use a number of systems to map the properties of that component to the properties of an SKNode. I accomplished this by creating a component wrapper around the SKNode and attaching it to each entity, and as entities are added to the scene, the systems updated a node property in each component that depended on SpriteKit with a reference to that entity’s node component. The sloppiness of that approach was that there were a lot of systems and the SKNode had to be added explicitly to each entity that relied on SpriteKit.

The revised approach eliminates those two problems by doing this instead:

  1. Use a single system to map all SpriteKit-dependent components to the associated SKNode.
  2. Detect when an entity will require a SKNode and automatically generate it.

By putting the detect and generate code in the same system as the mappings, not only do we not have to explicitly add a node to each entity, but the need to wrap the SKNode in a component class is also eliminated. Components are separated into three categories: those that require SpriteKit (like a sprite), those that might use SpriteKit (like a position), and those that have nothing to do with SpriteKit (like enemy AI). The SpriteKit system will only accept entities that contain a SpriteKit-required component, so no unnecessary SKNode objects will be created.

The SpriteKit system is simple:

class LGSpriteKitSystem: LGSystem
{
	var scene: SKScene
	
	init(scene: SKScene)
	{
		self.scene = scene
		
		super.init()
		self.updatePhase = .None
	}
	
	override func accepts(entity: LGEntity) -> Bool
	{
		return entity.has(LGSprite) || entity.has(LGPhysicsBody)
	}
	
	override func add(entity: LGEntity)
	{
		var node: SKNode!
		
		if let sprite = entity.get(LGSprite)
		{
			node = SKSpriteNode()
			sprite.node = node as SKSpriteNode
		}
		else
		{
			node = SKNode()
		}
		
		if let position = entity.get(LGPosition)
		{
			node.position.x = CGFloat(position.x)
			node.position.y = CGFloat(position.y)
			position.node = node
		}
		
		if let physicsBody = entity.get(LGPhysicsBody)
		{
			node.physicsBody = physicsBody.skphysicsbody
		}
		
		scene.addChild(node)
	}
}

The recommended usage is for both SpriteKit-required and SpriteKit-optional components to have an implicitly unwrapped optional SKNode property called node. Components that require SpriteKit can assume that their node property will be set, and components that are SpriteKit-optional can use computed properties to decide whether to store or map properties, as in the following code from the position component:

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 } }
}

And, of course, components that don’t care about SpriteKit don’t need a node property.

This revised approach feels better, but perhaps not quite complete. My primary complaint is that the SpriteKit system must be the first system to which entities are added, because (at this point, anyways) the mapping must take place before other systems try to access a component’s node property. To facilitate that (and ensure that developers don’t forget to add the SpriteKit system), I’ve automatically added that system as the first system in every scene. Because the game is dependent on SpriteKit right now (even the scene class is a SKScene subclass, for that matter), it’s probably okay, but for a better long-term solution I would like to come up with a way to make it order independent.

Take a look at the repository and let me know what you think of this project! I’m trying to figure out an efficient way to create tile collisions without just using physics bodies on each tile (or even on groups of tiles). Using physics bodies seems like it would be slow for tile collisions, since tile collisions are so predictable, but I have not yet thought of a way to make them work with stacked physics bodies without making them static physics bodies themselves. If you have an idea, let me know!

Posted by Luke Godfrey

Luke Godfrey is a graduate student at the University of Arkansas, pursuing a PhD in Computer Science. He has created applications on a number of platforms, and is especially interested in mobile and web development.

View more posts from this author

Leave a Reply

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