In this tutorial, the fifth and final installment of the SpriteKit From Scratch series, we look at some advanced techniques you can use to optimize your SpriteKit-based games to improve performance and user experience.
This tutorial requires that you are running Xcode 7.3 or higher, which includes Swift 2.2 and the iOS 9.3, tvOS 9.2, and OS X 10.11.4 SDKs. To follow along, you can either use the project you created in the previous tutorial or download a fresh copy from GitHub.
The graphics used for the game in this series can be found on GraphicRiver. GraphicRiver is a great source for finding artwork and graphics for your games.
1. Texture Atlases
In order to optimize the memory usage of your game, SpriteKit provides the functionality of texture atlases in the form of the SKTextureAtlas class. These atlases effectively combine the textures you specify into a single, large texture that takes up less memory than the individual textures on their own.
Luckily, Xcode can create texture atlases very easily for you. This is done in the same asset catalogs that are used for other images and resources in your games. Open your project and navigate to the Assets.xcassets asset catalog. At the bottom of the left sidebar, click the + button and select the New Sprite Atlas option.
![]() |
![]() |
- let obstaclesAtlas = SKTextureAtlas(named: "Obstacles")
In the MainScene class, add the following code at the end of the didMoveToView(_:) method:
- override func didMoveToView(view: SKView) {
- ...
- obstaclesAtlas.preloadWithCompletionHandler {
- // Do something once texture atlas has loaded
- }
- }
- func spawnObstacle(timer: NSTimer) {
- if player.hidden {
- timer.invalidate()
- return
- }
- let spriteGenerator = GKShuffledDistribution(lowestValue: 1, highestValue: 2)
- let texture = obstaclesAtlas.textureNamed("Obstacle \(spriteGenerator)")
- let obstacle = SKSpriteNode(texture: texture)
- obstacle.xScale = 0.3
- obstacle.yScale = 0.3
- let physicsBody = SKPhysicsBody(circleOfRadius: 15)
- physicsBody.contactTestBitMask = 0x00000001
- physicsBody.pinned = true
- physicsBody.allowsRotation = false
- obstacle.physicsBody = physicsBody
- let center = size.width/2.0, difference = CGFloat(85.0)
- var x: CGFloat = 0
- let laneGenerator = GKShuffledDistribution(lowestValue: 1, highestValue: 3)
- switch laneGenerator.nextInt() {
- case 1:
- x = center - difference
- case 2:
- x = center
- case 3:
- x = center + difference
- default:
- fatalError("Number outside of [1, 3] generated")
- }
- obstacle.position = CGPoint(x: x, y: (player.position.y + 800))
- addChild(obstacle)
- obstacle.lightingBitMask = 0xFFFFFFFF
- obstacle.shadowCastBitMask = 0xFFFFFFFF
- }
2. Saving and Loading Scenes
SpriteKit also offers you the ability to easily save and load scenes to and from persistent storage. This allows players to quit your game, have it relaunched at a later time, and still be up to the same point in your game as they were before.
The saving and loading of your game is handled by the NSCoding protocol, which the SKScene class already conforms to. SpriteKit's implementation of the methods required by this protocol automatically allow for all of the details in your scene to be saved and loaded very easily. If you want to, you can also override these methods to save some custom data along with your scene.
Because our game is very basic, we are going to use a simple Bool value to indicate whether the car has crashed. This shows you how to save and load custom data that is tied to a scene. Add the following two methods of the NSCoding protocol to the MainScene class.
- // MARK: - NSCoding Protocol
- required init?(coder aDecoder: NSCoder) {
- super.init(coder: aDecoder)
- let carHasCrashed = aDecoder.decodeBoolForKey("carCrashed")
- print("car crashed: \(carHasCrashed)")
- }
- override func encodeWithCoder(aCoder: NSCoder) {
- super.encodeWithCoder(aCoder)
- let carHasCrashed = player.hidden
- aCoder.encodeBool(carHasCrashed, forKey: "carCrashed")
- }
Next, add the following method to the MainScene class. The saveScene() method creates an NSData representation of the scene, using the NSKeyedArchiver class. To keep things simple, we store the data in NSUserDefaults.
- func saveScene() {
- let sceneData = NSKeyedArchiver.archivedDataWithRootObject(self)
- NSUserDefaults.standardUserDefaults().setObject(sceneData, forKey: "currentScene")
- }
- func didBeginContact(contact: SKPhysicsContact) {
- if contact.bodyA.node == player || contact.bodyB.node == player {
- if let explosionPath = NSBundle.mainBundle().pathForResource("Explosion", ofType: "sks"),
- let smokePath = NSBundle.mainBundle().pathForResource("Smoke", ofType: "sks"),
- let explosion = NSKeyedUnarchiver.unarchiveObjectWithFile(explosionPath) as? SKEmitterNode,
- let smoke = NSKeyedUnarchiver.unarchiveObjectWithFile(smokePath) as? SKEmitterNode {
- player.removeAllActions()
- player.hidden = true
- player.physicsBody?.categoryBitMask = 0
- camera?.removeAllActions()
- explosion.position = player.position
- smoke.position = player.position
- addChild(smoke)
- addChild(explosion)
- saveScene()
- }
- }
- }
Finally, open ViewController.swift and replace the viewDidLoad() method with the following implementation:
- override func viewDidLoad() {
- super.viewDidLoad()
- let skView = SKView(frame: view.frame)
- var scene: MainScene?
- if let savedSceneData = NSUserDefaults.standardUserDefaults().objectForKey("currentScene") as? NSData,
- let savedScene = NSKeyedUnarchiver.unarchiveObjectWithData(savedSceneData) as? MainScene {
- scene = savedScene
- } else if let url = NSBundle.mainBundle().URLForResource("MainScene", withExtension: "sks"),
- let newSceneData = NSData(contentsOfURL: url),
- let newScene = NSKeyedUnarchiver.unarchiveObjectWithData(newSceneData) as? MainScene {
- scene = newScene
- }
- skView.presentScene(scene)
- view.insertSubview(skView, atIndex: 0)
- let left = LeftLane(player: scene!.player)
- let middle = MiddleLane(player: scene!.player)
- let right = RightLane(player: scene!.player)
- stateMachine = LaneStateMachine(states: [left, middle, right])
- stateMachine?.enterState(MiddleLane)
- }
Run your app and run into an obstacle with your car. At this stage, you don't see a difference. Run your app again, though, and you should see that your scene has been restored to exactly how it was when you just crashed the car.
Written by Davis Allie
If you found this post interesting, follow and support us.
Suggest for you:
iOS 10 Projects: Build Amazing Apps with Apple's Newest iOS
iOS 10 & Swift 3: From Beginner to Paid Professional
Complete Beginners Guide to iOS Development - Build 10 Apps
The Complete iOS 10 Developer - Build Real Apps with Swift 3
Swift 3 and iOS 10 The Final Course Learn to Code like a Pro


No comments:
Post a Comment