SpriteKit Tutorial: Create an Interactive Children’s Book with SpriteKit and Swift 3

Caroline Begbie

Update 01/20/17: Updated for Swift 3, iOS 10 and Xcode 8 by Caroline Begbie. Original tutorial by Tammy Coron, and previously updated by Jorge Jordán.

Learn how to create an interactive children’s book for the iPad!

Learn how to create an interactive children’s book for the iPad!

With the iPad, it’s never been a better time to be a kid!

Using Apple’s SpriteKit, the iPad allows developers to create beautiful interactive children’s books that simply cannot be replicated in any other medium. For some examples, check out The Monster at the End of This Book, Bobo Explores Light, and Wild Fables.

In this tutorial, you’ll create an interactive children’s book named The Seasons using the SpriteKit framework, where you’ll learn how to add objects to scenes, create animation sequences, allow the reader to interact with the book’s characters and even how to add sound and music to your book!

If you’re a beginner to SpriteKit, you’ll find out how much you can do visually in the SpriteKit scene editor without writing any code at all. When you do have to write some code, you’ll discover how easy it is to build on top of your scene.

Note: The Seasons was written and illustrated by Tutorial Team member Tammy Coron. This tutorial uses the first few pages from that book. Please do not reproduce any of its contents in your own projects. The narration is provided by Amy Tominac, and again cannot be used in your own projects.

This tutorial also uses music from Kevin MacLeod and sound effects from FreeSound. These are both great resources for your project, but you must provide attribution. See attribution.txt in the project bundle for more details.

Getting Started

First, download the starter project for this tutorial. Extract the project to a convenient location on your drive, then open it up in Xcode.

The starter project contains all the images and sounds needed for this tutorial. It also contains pre-built pages (or scenes) that you’ll load up after creating the title page scene. In the project settings, the app is set to run on an iPad in landscape. Build and run the app, and see a blank grey scene.

The following diagram shows the final structure of the app.

game-scene-relationship

When the app starts, Main.storyboard loads up GameViewController. Open GameViewController.swift and find these lines in viewDidLoad():

if let scene = SKScene(fileNamed: "GameScene") {
   // ...
}

This code loads a file named GameScene.sks, which is the SpriteKit scene file that you can edit in the visual SpriteKit scene editor.

In this book, each page will be a scene. Therefore, you’ll use the scene editor to add the sprites, sounds and animations for each page. Open GameScene.sks. On the right hand pane, take a look at the Custom Class Inspector and see that the class associated with GameScene.sks is GameScene.

gameScene-class

When GameScene.sks loads up, it creates an instance of GameScene. You’ll be able to add code to GameScene.swift to interact with elements created in the editor.

All the pages will be subclasses of GameScene. Any code that is specific to one page will go in the subclass, but any code that is the same for all pages will go in GameScene.

Now you’re ready to get started. Like any good book, it’s best to start at the beginning — the title scene.

Creating a Page

Create a new file for the title page. Click File/New/File… and choose the iOS/Resource/SpriteKit Scene template. Click Next and name the file TitlePage. Click Create to create and open the new scene in the scene editor.

With TitlePage.sks open, zoom out so that you can see the whole scene. You can use the + and at the bottom right of the editor to zoom in and out.

empty-scene

The scene is currently in portrait, but your book is going to be in landscape. In the Attributes Inspector, change the following:

  • Size: W: 1024, H: 768
Note: If you have trouble clicking in a text box and changing the value, try clicking the previous or next box and pressing Tab or Shift+Tab to go to the next or previous box. The box will then become editable.

This will resize the scene to the correct aspect for the iPad in landscape. The iPad Pro is a different size, but still the same aspect ratio, so pages will scale to fit correctly.

You’re going to add a background image to the scene. Below the Attributes Inspector, at the bottom right of the screen, locate the Media library. All the images that are in Assets.xcAssets are listed here.

media-library

Locate the image named background-titlepage. Drag the image onto the scene. The editor will automatically create a sprite node using this image.

sprite-created

With the background image selected, change the following in the Attributes Inspector:

  • Name: background
  • Position: X: 0, Y:0

This will center the image in the scene.

This diagram shows how to position your nodes in scenes:

dimensions

The scene’s anchor point defaults to (0.5, 0.5) in the editor. This means that any node with a zero position and the same anchor point will have its center halfway across the scene and halfway down the scene. With a scene height of 768, a Y value of 384 is at the top of the scene, and a Y value of -384 is at the bottom of the scene.

Drag title_text from the Media library to the scene. In the Attributes Inspector, change the following:

  • Name: titleText
  • Position: X: -120, Y: 150

Now drag on button_read from the Media library and position it under the title. Set its Name and Position as follows:

  • Name: readButton
  • Position: X: -100, Y: 12

GameViewController should load your new scene. In GameViewController.swift change this line:

if let scene = SKScene(fileNamed: "GameScene") {

to

if let scene = SKScene(fileNamed: "TitlePage") {

This will ensure that your new scene loads when you run the app.

Build and run the app and see how easy it is to construct a page.

simulator-title1

Just as in the above screenshot, you may find that either “Seasons” or “Read Story” is missing. Currently the sprites are all rendered in a random order. Run the app a few times, and your two smaller sprite nodes will appear and disappear randomly.

Sprite Rendering Order

Your scene consists of a tree of nodes. Each node may have one or more children. If the node has children, it is considered the parent of the child nodes.

scene-hierarchy1

At the top of this tree is the scene itself. Your scene currently has three child nodes all held in an array named children, which is a property on SKNode. SKScene and SKSpriteNode are subclasses of SKNode.

scene-hierarchy2

Each of the sprite nodes that you’ve dragged into the scene are held in children. An array has an implicit order, but the scene rendering currently ignores this.

There are two ways of dealing with this.

First, in GameViewController, the template created this line:

view.ignoresSiblingOrder = true

ignoresSiblingOrder, when set to true, means that the rendering order of sibling nodes is arbitrary. It may appear that nodes are missing from your app, but they are actually rendering behind the background sprite.

In GameViewController, set this value to false:

view.ignoresSiblingOrder = false

Build and run the app, and all your sprites should appear appropriately in your scene.

The second way of dealing with this is to set the Z position of each sprite. The Z position is a depth position and determines how far forward your nodes are. A node with a lower Z position will always appear behind nodes with a higher Z position.

This gives you total control over the depth order that the sprites appear in, so this is the method you will be using.

Change the code back to:

view.ignoresSiblingOrder = true

Open TitlePage.sks and select background in the scene navigator. Change the position as follows:

  • Position: Z: -10

The other sprites will keep their default Z positions of 0.

Build and run the app, and your background page will appear behind the other two sprites.

Adding Animation to Objects

SpriteKit makes it really easy to add animation to the objects in your scene. Instead of simply having the title appear on the screen, you can make it slide onto the screen and bounce a little before it comes to rest.

With TitlePage.sks open, select titleText. At the bottom of the scene, ensure the Action Editor is displaying. If it’s not, at the bottom left of the scene, click the right hand icon to show it.

animate-icon

In the Object library, find Move Action and drag it onto titleText in the Action Editor.

titleText

You’ve now created an animation block on the timeline.

With the new animation block selected, in the Attributes Inspector, change the following:

  • Start Time: 0
  • Duration: 3
  • Timing Function: Linear
  • Offset: X: 0, Y: -300

Click on the word Animate above the Action Editor. The editor will switch to Animate mode. As soon as you click on Animate, the animation of the title text takes place. The title will move down by three hundred points over three seconds. Watch as the playhead moves along the timeline. You can drag the playhead to see the action happen more slowly.

animated1

Notice how the title ends up below the read button. The title should start off the top of the page and animate down the page to just above the read button. Click on the word Layout above the Action Editor to exit Animate mode.

Select titleText in the scene navigator. Change the following:

  • Position: X: -120, Y: 450

This will move “The Seasons” to the top outside the page.

Build and run the app and watch “The Seasons” gently scroll down the page into position.

This is a simple animation, but you should be able to see the potential. You’ll now add a little bounce to the animation you just created and also fade in the read button after the title animation has finished.

Select the move animation in the timeline. In the Attributes Inspector, change the timing function from linear to the following:

  • Timing Function: Ease In

Linear means that the animation will happen at the same rate for the whole animation. Ease In means that the animation will be slower towards the start and then speed up.

Drag a second and third Move action onto the titleText Action timeline, one after another.

action-editor-move

Select the second Move action and in the Attributes Inspector, change the following:

  • Start Time: 3
  • Duration: 0.25
  • Offset: X: 0, Y: 5
Note: If you’re having trouble entering the values, make sure that you are in Layout mode. Confusingly you should have the word Animate at the bottom left, not the word Layout.

Select the third Move action and in the Attributes Inspector, change the following:

  • Start Time: 3.25
  • Duration: 0.25
  • Offset: X: 0, Y: -5

Build and run the app, and watch the title text animate in, slow at first, but then gaining speed. Watch for the little bump at the end of the animation.

Now for the fade-in on the Read button.

Select readButton in the scene navigator. In the Attributes Inspector, change the following:

  • Alpha: 0

With its alpha property set to 0, readButton should disappear.

Drag a FadeIn action from the Objects library onto the readButton’s timeline in the Action editor.

With the FadeIn action selected, change the following in the Attributes Inspector:

  • Start Time: 3.25
  • Duration: 0.75

Drag in a PlaySoundFileNamed action to readButton’s timeline after the FadeIn action.

In the Attributes Inspector, change the following:

  • Start Time: 4.0
  • Filename: thompsonman_pop

Build and run the app, and watch the completed title page animation with a popping sound at the end. Tapping the read button doesn’t do anything — yet.

Note: The simulator has a low frame rate for SpriteKit animations. To get the full effect, you may have to run the app on a real iPad. You can see the frame rate at the bottom right of the app.

Architecture of the App

Each page of your book will have similar elements:

  • Background audio
  • Text audio (most pages)
  • A footer with touch enabled for page navigation
  • Transition between pages

Currently when TitlePage.sks loads, it creates a generic SKScene instance, but you’ll deal with these common elements in the SKScene subclass GameScene. Each page will be a subclass of GameScene.

Create a new file. Choose File/New/File…, choose iOS/Source/Cocoa Touch Class and click Next. Name the class TitlePage and make it a subclass of GameScene. Click Next and then Create.

In TitlePage.swift, replace:

import UIKit

with:

import SpriteKit

When you import the SpriteKit framework, all supporting frameworks such as UIKit and Foundation are automatically added.

In TitlePage.sks, select Scene in the scene navigator and in the Custom Class Inspector, change the following:

  • Custom Class: TitlePage

This ties the SpriteKit scene to the custom class that you’ve just created.

Reference Nodes

Each page is going to have a footer with navigation buttons. As this footer will be the same across all pages, you’ll use a separate scene for it, and then add a reference to this footer in all the page scenes.

I’ve already created an empty scene named Footer.sks, and added a reference to this scene in all the page scenes except TitlePage.sks.

Before starting, take a look at Scene01.sks. You’ll see a reference node for the footer in the scene navigator, but no footer images are showing up.

footer-node

You’ll now add sprite nodes to Footer.sks, and see the real advantage of creating a reference node. All the page scenes with a reference to this Footer will automatically update with the new nodes.

Open Footer.sks and drag the footer image from the Media library onto the scene.

In the Attributes Inspector, change the following:

  • Name: footerBackground
  • Position: X: 0, Y: 0, Z: -5

Drag button_previous, button_next and button_sound_on onto the footer background and position them on the right of the bar like so:

footer-buttons

When the user taps the left hand side of the footer background, she’ll return to the title page. There’s no special button image for this, so from the Object library, drag a Color Sprite to the left hand side and size and position it to cover the text on the background:

button-home

Change the following:

  • Position: Z: -20

This will position the red sprite behind everything so that it won’t show.

In the Attributes Inspector, name each sprite appropriately. From left to right:

  • buttonHome
  • buttonSound
  • buttonPrevious
  • buttonNext

Open Scene01.sks again. Like magic, the footer now appears as you designed it in Footer.sks.

footer-updated

That’s the power of reference nodes. If you change a scene, those changes will propagate to all other scenes that have a reference to the changed scene.

You can now add the footer to your Title Page. Open TitlePage.sks, and drag a Reference node from the Object Library to the scene.

With this reference node selected, change:

  • Name: footer
  • Reference: Footer
  • Position: X: 0, Y: -345, Z: 100

Build and run, and you’ll see the footer at the bottom of the title page.

simulator-title-footer

Currently the buttons on the footer don’t actually do anything. This is because the sprites don’t have any touch event handlers. In the next section, you’ll dive into code and connect the properties in your scene that you created in the editor.

Detecting Touch Events

When SpriteKit loads TitlePage.sks, it expands into a scene hierarchy that looks like this:

node-hierarchy

You’ll hook into this hierarchy and connect the nodes to properties in code using the name you assigned in the editor.

Because the footer buttons will do the same thing across all scenes, you’ll first change the code in the base class GameScene. This class has a few methods in it already. These methods are just simple touch event handlers and stub methods for the page scenes to override.

In GameScene.swift, add these properties to GameScene:

var footer:SKNode!
var btnNext: SKSpriteNode!
var btnPrevious: SKSpriteNode!
var btnSound: SKSpriteNode!
var btnHome: SKSpriteNode!

Each of these properties will match a sprite in Footer.sks. To connect them, add this method at the bottom:

override func sceneDidLoad() {
  super.sceneDidLoad()
  footer = childNode(withName: "footer")
}

SpriteKit calls sceneDidLoad() after the scene has loaded in memory, and you can very easily locate the child node using the name you gave it in the scene editor.

However childNode(withName:) only locates nodes that are directly descended from the parent. To retrieve nodes further down the hierarchy, you add two forward slashes // to the start of the search string. This will perform a recursive search across the whole node tree.

Add this to the end of sceneDidLoad():

btnNext = childNode(withName: "//buttonNext") as! SKSpriteNode
btnPrevious = childNode(withName: "//buttonPrevious") as! SKSpriteNode
btnSound = childNode(withName: "//buttonSound") as! SKSpriteNode
btnHome = childNode(withName: "//buttonHome") as! SKSpriteNode

Now you have references in code to all the navigator buttons in the footer.

Create a new method to transition to a different scene:

func goToScene(scene: SKScene) {
  let sceneTransition = SKTransition.fade(with: UIColor.darkGray, duration: 1)
  scene.scaleMode = .aspectFill
  self.view?.presentScene(scene, transition: sceneTransition)
}

This method creates a fade transition, and then the view presents the specified scene with that transition.

There are two stub methods in GameScene: getNextScene() and getPreviousScene(). These methods will be overridden by each page to return the correct next and previous scenes for that page.

In TitlePage.swift, override the method to return the next scene:

override func getNextScene() -> SKScene? {
  return SKScene(fileNamed: "Scene01") as! Scene01
}

In Scene01.swift add:

override func getPreviousScene() -> SKScene? {
  return SKScene(fileNamed: "TitlePage") as! TitlePage
}

With all that in place, you can now override the touch handler in GameScene.swift by adding this method:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
  guard let touch = touches.first else { return }
    
  // 1
  let touchLocation = touch.location(in: self)

  // 2
  if footer.contains(touchLocation) {
    let location = touch.location(in: footer)

    // 3
    if btnNext.contains(location) {
      goToScene(scene: getNextScene()!)
    } else if btnPrevious.contains(location) {
      goToScene(scene: getPreviousScene()!)
    } else if btnHome.contains(location) {
      goToScene(scene: SKScene(fileNamed: "TitlePage") as! TitlePage)
    }
     
  } else {

    // 4
    touchDown(at: touchLocation)
  }
}

Going through this code in sequence:

  1. You retrieve the location of the first touch within the whole screen.
  2. If the touch is in the area of the footer, get the location of the touch within the footer.
  3. You’re then able to check whether each button contains that location and take the appropriate action.
  4. If the touch is not within the footer, call touchDown(at:). Each page will have different interactive features, so you’ll override this method in each page subclass.

Run the app and check out all the buttons. The next button should take you to the next scene, and the previous button should take you to the previous scene.

You can now read the book all the way through!

However, what happens if you click the next button on the last page, or the previous button on the title page? Since there is no next or previous page provided for these, the app crashes.

The title page actually has a read button to start the book, so it doesn’t need the next and previous buttons. Also, the last page shouldn’t display the next button.

In TitlePage.swift, add a property to TitlePage for the read button:

var readButton: SKSpriteNode!

Override sceneDidLoad() as follows:

override func sceneDidLoad() {
  super.sceneDidLoad()
  readButton = childNode(withName: "readButton") as! SKSpriteNode
    
  btnNext.isHidden = true
  btnPrevious.isHidden = true
}

Here you get a reference to the read button node and hide the next and previous buttons.

Add an action to the read button by overriding touchDown(at:):

override func touchDown(at point: CGPoint) {
  if readButton.contains(point) {
    goToScene(scene: getNextScene()!)
  }
}

When the read button is tapped, the next scene will be loaded.

In summary, when a touch action happens, GameScene’s touchesBegan(_:with:) is performed. This does the test to see if the touch was within the footer. If it wasn’t, then it calls touchDown(at:). Each scene subclass can override this method to perform actions suitable for that page.

Build and run the app, and your title page now has the navigation buttons hidden. Tap the read button and the first page in the book will load up.

Tap the left hand area of the footer, and the title page will show. That’s where you hid the home button behind the footer.

simulator-navigation-complete

Your book is really starting to take shape.

Sounds are an integral part of any interactive children’s book and SpriteKit makes this tremendously easy as well!

Adding Sound to Your Story

With the pop sound, you’ve already seen how to add simple sounds to your page. Each page will also have background music. The user will be able to disable this background music by tapping the sound button.

In TitlePage.sks, drag an Audio node from the Object library to the page. It doesn’t really matter where — just drag it to the center of the page.

In the Attributes Inspector, change the following:

  • Name: backgroundMusic
  • Filename: title_bgMusic
  • Autoplay: unchecked

In GameScene.swift, add these properties to GameScene:

var backgroundMusic: SKAudioNode?
var textAudio: SKAudioNode?
var soundOff = false 

In GameScene, add this to the end of sceneDidLoad():

backgroundMusic = childNode(withName: "backgroundMusic") as? SKAudioNode
textAudio = childNode(withName: "textAudio") as? SKAudioNode

This connects up the nodes in the editor with the nodes in code.

backgroundMusic will contain the background music and textAudio will contain the text that will be narrated. The music should play — or not – depending on the user’s preference. When the user taps the sound button, the music should turn off and the button’s image should show that the sound is disabled.

Add this conditional to the list of conditionals in touchesBegan(_:with:):

else if btnSound.contains(location) {
  soundOff = !soundOff
}

This simply switches the Boolean value.

Change the property declaration of soundOff from this:

var soundOff = false

to:

var soundOff = false {
  didSet {
    // 1
    let imageName = soundOff ? "button_sound_off" : "button_sound_on"
    btnSound.texture = SKTexture(imageNamed: imageName)
      
    // 2
    let action = soundOff ? SKAction.pause() : SKAction.play()
    backgroundMusic?.run(action)
    backgroundMusic?.autoplayLooped = !soundOff
      
    // 3
    UserDefaults.standard.set(soundOff, forKey: "pref_sound")
    UserDefaults.standard.synchronize()
  }
}

This code will be run whenever soundOff is changed. Going through the code:

  1. You change the texture of the image depending on the value of soundOff.
  2. You create and run a SKAction again depending on the value of soundOff. If the sound should be on, it’s a play action; otherwise it’s a pause action. You also set autoplayLooped so that the music will be looped if the sound is on.
  3. You save the preferences to UserDefaults. This is a very convenient way of persisting settings between app performance.

You’ll load up the user’s preference from UserDefaults when the scene is loaded. Add this to the end of sceneDidLoad():

soundOff = UserDefaults.standard.bool(forKey: "pref_sound")

Build and run the app, and tap the sound button. The background music should pause or play and the texture should change. Quit the app with the music off, and run the app again. Because the setting is saved in UserDefaults the app should remember that the background music is off.

To read the text, each page will have a text audio file. I’ve already added these audio nodes to each page.

When each page has been loaded, there should be a slight delay and then the text audio should play.

In GameScene.swift, add this method to GameScene:

override func didMove(to view: SKView) {
  if let textAudio = textAudio {
    let wait = SKAction.wait(forDuration: 0.2)
    let play = SKAction.play()
    textAudio.run(SKAction.sequence([wait, play]))
  }
}

SpriteKit calls didMove(to:) after it loads the scene and adds it to the view. Here you check whether the page has some text audio, and if it does, you create a sequence of actions. First you create an action to wait, and then an action to play. Then you run these actions in sequence on the text audio.

Build and run the app, and have the story read to you! :]

simulator-read

All these audio settings were done in GameScene, and as all the page scenes are subclasses of GameScene with the same footer node, these audio setting will work on all pages.

An Introduction to Physics

You can really improve the appeal of your book by adding some interactivity. The main character is a boy, and in this section, you’re going to set up his hat so that the reader can drag it around the screen and place it on the boy’s head.

I’ve already added the hat and the boy (with a blinking action) to Scene01.sks, but you will change the physics for his hat. It’s really easy and fun to add physics in the editor.

In Scene01.sks, select hat in the scene navigator.

In the Attributes Inspector, under Physics Definition (you’ll probably have to scroll down to see this section), change the following:

  • Body Type: bounding rectangle
  • Dynamic: checked
  • Allows Rotation: unchecked
  • Restitution: 0.5

You can change a number of physical characteristics of your objects, such as mass, gravity and friction effects.

Currently you have no object for the hat to collide with. Click Animate and watch the hat fall off the bottom of the screen.

The easiest way to deal with this is to set up a physics edge in code.

In Scene01.swift, add the following property to Scene01:

var hat: SKSpriteNode!

Override sceneDidLoad() to connect up the property:

override func sceneDidLoad() {
  super.sceneDidLoad()
  hat = childNode(withName: "hat") as! SKSpriteNode
}

Add this code to the end of sceneDidLoad():

var bounds = CGRect.zero
bounds.origin.x = -size.width/2
bounds.origin.y = -size.height/2 + 110
bounds.size = size
physicsBody = SKPhysicsBody(edgeLoopFrom: bounds)

Here you set up a bounding rectangle of the size of the screen, less a margin at the bottom. You set the scene’s physics body to have an edge loop of the size of this rectangle, and the hat will stay within this physics body.

Build and run the app, and the hat will rest on the bottom of the screen 110 points up.

simulator-hat-rest

Okay, you’ve added some physics properties to the hat — but how do you go about adding interactivity?

Handling Touches and Moving the Hat

This section implements the touch handling for the hat so that you can move it around the screen.

In Scene01.swift, add a new property to Scene01 to hold the touch location:

var touchPoint: CGPoint?

touchPoint will contain the most recent touch location when user touches the screen, but will be nil whenever the user is not touching the screen.

Add the following method to Scene01:

override func touchDown(at point: CGPoint) {
  if hat.contains(point) {
    touchPoint = point
      
    hat.physicsBody?.velocity = CGVector.zero
    hat.physicsBody?.angularVelocity = 0
    hat.physicsBody?.affectedByGravity = false
  }
}

When the user first touches the hat, the code stores the location in touchPoint. It also makes a few changes to the hat’s physics body. Without these changes, it’s virtually impossible to drag the hat around the scene because you’re constantly fighting with the physics engine.

You’ll need to track the touch as it moves across the screen, so add the following method to Scene01:

override func touchMoved(to point: CGPoint) {
  if touchPoint != nil {
    touchPoint = point
  }
}

Here you update the most recent touch location stored in touchPoint.

When the user stops touching the screen, you need to reset any hat-related data. Add the following method:

override func touchUp(at point: CGPoint) {
  if let touchPoint = touchPoint {
    hat.physicsBody?.affectedByGravity = true
  }
  touchPoint = nil
}

You’ll have a compiler warning with this code, but you’ll fix that in a minute.

There’s just one more thing to do to get the hat to track the user’s finger as it moves on the screen.

Add the following method to Scene01:

override func update(_ currentTime: TimeInterval) {
  if let touchPoint = touchPoint {
    hat.position = touchPoint
  }
}

SpriteKit invokes update(_:) before rendering each frame. Here you check to see if the user is dragging the hat; if they are, change hat’s position to the location stored in touchPoint. The hat can’t go off the screen because it’s enclosed by the physics edge.

Build and run your project; tap the read button and play around with the hat on the screen for a while. Drop the hat and watch it bounce!

simulator-hat-physics

Moving the hat around is cool, but there isn’t any feedback as to whether or not the hat is on top of the boy’s head. It’s time to add that feedback.

Just as you created a dummy home button for going back to the title page, I’ve added a dummy sprite node named hatPosition to Scene01.sks behind all the other nodes to indicate the position of the hat. You’ll be able to use this sprite node’s position to check whether the hat is in the correct place.

In Scene01.swift, in touchUp(at:) after:

hat.physicsBody?.affectedByGravity = true

add this code:

if let hatPosition = childNode(withName: "hatPosition") {
  if hatPosition.contains(touchPoint) {
   hat.position = hatPosition.position
   hat.physicsBody?.affectedByGravity = false
   hat.run(SKAction.playSoundFileNamed("thompsonman_pop.mp3",
                                       waitForCompletion: false))
  }
}

Here you check whether the hat is in the correct position, by checking whether touchPoint is inside the dummy sprite node. If it is, then the hat is close enough, so you position the hat on the kid’s head. You also play a pop sound to alert the user that the hat is now firmly placed on the main character’s head – which is important! Did you see all that snow outside the window? Brrrr!

Build and run your project; grab the hat and plunk it down on the boy’s head like so:

simulator-hat-on-head

Aside from the story and the narration, these interactive elements with actions and sounds are key to the experience and really take advantage of what iOS and SpriteKit have to offer.

Particles

These pages about winter and snow are lacking an important ingredient — snow!

In my opinion, no interactive book is complete without particles. These are elements like fire and rain and explosions and snow. A particle is usually one small image. This image is repeated many times in different sizes, colors and directions.

SpriteKit particles are really easy to create with the SpriteKit Particle Editor. Click File/New/File…. Choose the iOS/Resource/SpriteKit Particle File template and click Next.

From the Particle Template dropdown, choose Snow. I encourage you to experiment with the other particle templates later.

Click Next and name the file Snow, then click Create. The particle file will open and “real” snow will animate in front of you.

snow

Each particle is an instance of a spark texture, but the image is sized differently for each particle. The snowflakes also descend in different speeds and directions. Playing with particles is unfortunately beyond the scope of this tutorial, but all these settings can be changed in the Attributes Inspector.

Open Scene01.sks. You’ll notice that there are two backgrounds here. The backgrounds are exactly the same except that backgroundAlpha has a transparent section for the window. You’ll sandwich the snow between these two backgrounds.

Drag your Snow.sks file from the Project navigator to the top center of the window in the background image. Change:

  • Name: snow
  • Position: X: 263, Y: 200, Z: -8

Here you set the Z position so that the emitter is sandwiched between the two background nodes.

Click Animate to see the snow fall outside the window! Now it’s looking really wintry.

simulator-final

Where To Go From Here?

I hope you enjoyed working through this tutorial. I love working with Tammy Coron’s art. :]

At this point the story is up to you! You can download the complete sample project and compare notes if you’d like.

simulator-snowman

If you want to learn more about SpriteKit, be sure to check out our book 2D Apple Games by Tutorials.

If you have any questions or comments, feel free to join in the discussion below!

Team

Each tutorial at www.raywenderlich.com is created by a team of dedicated developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Caroline Begbie

I’m an indie iOS developer. When I’m not developing, I’m playing around with 2D and 3D animation software, or learning Arduino and electronics.
In my past I’ve taught the elderly how to use their computers, done marionette shows for pre-schools, and created accounting and stock control systems for mining companies.

Other Items of Interest

Save time.
Learn more with our video courses.

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 20 total!

Swift Team

... 15 total!

iOS Team

... 42 total!

Android Team

... 16 total!

macOS Team

... 11 total!

Unity Team

... 11 total!

Articles Team

... 12 total!

Resident Authors Team

... 15 total!