How To Make a Game Like Cut the Rope Using SpriteKit and Swift

Kevin Colligan
Snip The Vine

Ever feed a pineapple to a crocodile? In this tutorial, you’ll find out how!

Update 01/20/2017: Updated for iOS 10, Xcode 8 and Swift 3 by Kevin Colligan. Original post by Tammy Coron, and previously updated by Nick Lockwood.

Cut The Rope is a popular physics-driven game where players feed a little monster named Om Nom by cutting ropes which suspend candies. Slice at just the right time and place, and Om Nom gets a tasty treat.

With all due respect to Om Nom, the game’s true star is its simulated physics: ropes swing, gravity pulls and candies tumble just as you would expect in real life.

You can build a similar experience using the physics engine of SpriteKit, Apple’s 2D game framework. In this tutorial, you’ll do just that, with a game called Snip The Vine.

Note: This tutorial assumes you have some experience with SpriteKit. If you’re new to SpriteKit, check out our SpriteKit Swift Tutorial for Beginners.

Getting Started

In Snip The Vine, you’ll feed cute little animals pineapples to a crocodile. To get started, download the starter project. Open the project in Xcode for a quick look at how it’s structured.

The project files are split across several folders. In this tutorial, you’ll work in the Classes folder, which contains the primary code files. Feel free to explore the other folders, shown below:

snipVineFolders

Setting Up Constants

Constants make your code more readable and maintainable by avoiding repetition of hard-coded strings or “magic numbers”.

Open Constants.swift and add the following code:

struct ImageName {
  static let Background = "Background"
  static let Ground = "Ground"
  static let Water = "Water"
  static let VineTexture = "VineTexture"
  static let VineHolder = "VineHolder"
  static let CrocMouthClosed = "CrocMouthClosed"
  static let CrocMouthOpen = "CrocMouthOpen"
  static let CrocMask = "CrocMask"
  static let Prize = "Pineapple"
  static let PrizeMask = "PineappleMask"
}

struct SoundFile {
  static let BackgroundMusic = "CheeZeeJungle.caf"
  static let Slice = "Slice.caf"
  static let Splash = "Splash.caf"
  static let NomNom = "NomNom.caf"
}

With this code you define some constants for things like sprite image names and sound files.

Beneath those, add the following:

struct Layer {
  static let Background: CGFloat = 0
  static let Crocodile: CGFloat = 1
  static let Vine: CGFloat = 1
  static let Prize: CGFloat = 2
  static let Foreground: CGFloat = 3
}

struct PhysicsCategory {
  static let Crocodile: UInt32 = 1
  static let VineHolder: UInt32 = 2
  static let Vine: UInt32 = 4
  static let Prize: UInt32 = 8
}

This code declares two more structs, Layer and PhysicsCategory, each containing a bunch of static CGFloat and UInt32 properties respectively. You’ll use these to specify the zPosition and physics category of a sprite when you add it to the scene.

Finally, add one more struct:

struct GameConfiguration {
  static let VineDataFile = "VineData.plist"
  static let CanCutMultipleVinesAtOnce = false
}

The VineDataFile defines the name of the file that determines where vines will be placed.

CanCutMultipleVinesAtOnce allows for an easy way to modify a gameplay parameter. It’s not always obvious which gameplay decisions will make your game more fun. Constants like this provide a simple way to “flip a switch” between approaches, so you can change your game later.

Now you can begin adding nodes to your scene.

Adding Background Sprites to the Scene

Open GameScene.swift and add the following to setUpScenery():

let background = SKSpriteNode(imageNamed: ImageName.Background)
background.anchorPoint = CGPoint(x: 0, y: 0)
background.position = CGPoint(x: 0, y: 0)
background.zPosition = Layer.Background
background.size = CGSize(width: size.width, height: size.height)
addChild(background)
    
let water = SKSpriteNode(imageNamed: ImageName.Water)
water.anchorPoint = CGPoint(x: 0, y: 0)
water.position = CGPoint(x: 0, y: 0) 
water.zPosition = Layer.Foreground
water.size = CGSize(width: size.width, height: size.height * 0.2139)
addChild(water)

The setUpScenery() method is called from didMove(). In this method, you create a couple of SKSpriteNodes and initialize them using SKSpriteNode(imageNamed:). To handle multiple screen sizes, you explicitly size the background images.

You’ve changed the anchorPoint of these two nodes from the default value of (0.5, 0.5) to (0, 0). This means the nodes are positioned relative to their bottom-left corner instead of their center, which allows you to easily position background and water in the scene with their bottoms aligned.

Note: The anchorPoint property uses the unit coordinate system, where (0,0) represents the bottom-left corner of the sprite image, and (1,1) represents the top-right corner. Because it’s always measured from 0 to 1, these coordinates are independent of the image dimensions and aspect ratio.

You also set the sprites’ zPositions, which controls the order in which SpriteKit draws nodes onscreen.

Recall that in Constants.swift you specified some values to use for the zPosition of sprites. You use two of them here — Layer.Background and Layer.Foreground — to ensure the background will always be behind all other sprites and the foreground will always be drawn in front.

Build and run your project. If you did everything right, you should see the following screen:

Screen1

Adding the Crocodile to the Scene

Be forewarned that this crocodile is quite snappy, please keep your fingers at a safe distance at all times! :]

Like the background scenery, the crocodile is represented by an SKSpriteNode. There are a couple of important differences though: You’ll need to retain a reference to the crocodile for your game logic, and you’ll need to set up a physics body for the crocodile sprite in order to detect and handle contacts with other bodies.

Still in GameScene.swift, add the following properties to the top of the class:

private var crocodile: SKSpriteNode!
private var prize: SKSpriteNode!

These properties will store references to the crocodile and the prize (pineapple). You’ve defined them as private, because they won’t be accessed outside of GameScene.

The type for these properties has been defined as SKSpriteNode!. The ! means that these are implicitly unwrapped optionals, which tells Swift that they don’t need to be initialized right away. Use this when you’re absolutely confident that they won’t be nil whenever you try to access them… otherwise the app will crash.

Locate the setUpCrocodile() method inside of GameScene.swift and add the following code:

crocodile = SKSpriteNode(imageNamed: ImageName.CrocMouthClosed)
crocodile.position = CGPoint(x: size.width * 0.75, y: size.height * 0.312)
crocodile.zPosition = Layer.Crocodile
crocodile.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: ImageName.CrocMask), size: crocodile.size)
crocodile.physicsBody?.categoryBitMask = PhysicsCategory.Crocodile
crocodile.physicsBody?.collisionBitMask = 0
crocodile.physicsBody?.contactTestBitMask = PhysicsCategory.Prize
crocodile.physicsBody?.isDynamic = false

addChild(crocodile)

animateCrocodile()

With this code you create the crocodile node and set its position and zPosition.

Unlike the background scenery, the croc has an SKPhysicsBody, which means it can interact physically with other objects in the world. This will be useful later for detecting when the pineapple lands in its mouth. You don’t want the croc to get knocked over, or fall off the bottom of the screen though, so you’ve set isDynamic to false which prevents it from being affected by physical forces.

The categoryBitMask defines what physics category the body belongs to — PhysicsCategory.Crocodile in this case. You set collisionBitMask to 0 because you don’t want the crocodile to bounce off of any other bodies. All you need to know is when a “prize” body makes contact with the crocodile, so you set the contactTestBitMask accordingly.

You may have noticed that the physics body for the crocodile is being initialized using an SKTexture object. You could instead simply re-use CrocMouthOpen for the body texture, but that image includes the croc’s whole body whereas the mask texture just includes the crocodile’s head and mouth. A croc can’t eat a pineapple with its tail!

Now you’ll add a “waiting” animation to the crocodile. Find the animateCrocodile() method and add the following code:

let duration = 2.0 + drand48() * 2.0
let open = SKAction.setTexture(SKTexture(imageNamed: ImageName.CrocMouthOpen))
let wait = SKAction.wait(forDuration: duration)
let close = SKAction.setTexture(SKTexture(imageNamed: ImageName.CrocMouthClosed))
let sequence = SKAction.sequence([wait, open, wait, close])

crocodile.run(SKAction.repeatForever(sequence))

Besides making the little crocodile very anxious, this code also creates a couple actions that change the texture of the crocodile node so that it alternates between a closed mouth and an open mouth.

The SKAction.sequence() constructor creates a sequence of actions from an array. In this case, the texture actions are combined in sequence with a randomly-chosen delay period between 2 and 4 seconds.

The sequence action is wrapped in a repeatActionForever() action, so it will repeat for the duration of the level. It is then run by the crocodile node.

That’s it! Build and run and see this fierce reptile snap his jaws of death!

Screen2

You’ve got scenery and you’ve got a croc — now you need cute little animals a pineapple.

Adding the Prize

Open GameScene.swift and locate the setUpPrize() method. Add the following:

prize = SKSpriteNode(imageNamed: ImageName.Prize)
prize.position = CGPoint(x: size.width * 0.5, y: size.height * 0.7)
prize.zPosition = Layer.Prize
prize.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: ImageName.Prize), size: prize.size)
prize.physicsBody?.categoryBitMask = PhysicsCategory.Prize
prize.physicsBody?.collisionBitMask = 0
prize.physicsBody?.density = 0.5
    
addChild(prize)

Similar to the crocodile, the pineapple node also uses a physics body. The big difference is that the pineapple should fall and bounce around, where as the crocodile just sits there and wait impatiently. So you leave isDynamic set to its default value of true. You also decrease the density of the pineapple so it can swing more freely.

Working with Physics

Before you start dropping pineapples, it’s a good idea to configure the physics world. Locate the setUpPhysics() method inside of GameScene.swift and add the following three lines:

physicsWorld.contactDelegate = self
physicsWorld.gravity = CGVector(dx: 0.0, dy: -9.8)
physicsWorld.speed = 1.0

This sets up the physics world’s contactDelegate, gravity and speed. Gravity specifies the gravitational acceleration applied to physics bodies in the world, while speed specifies the speed at which the simulation executes. (Both properties are set to their default values here.)

You’ll see a compiler error on the first line because you’ve specified self as the the contact delegate, but GameScene doesn’t conform to the SKPhysicsContactDelegate protocol. Fix that by adding that protocol to the class definition, like so:

class GameScene: SKScene, SKPhysicsContactDelegate {

Build and run the app again. You should see the pineapple sail past the crocodile and fall into the water (it actually falls behind the water). Time to add the vines.

Adding Vines

SpriteKit physics bodies are designed to model rigid objects. But vines bend. So you’ll implement each vine as an array of segments with flexible joints, similar to a chain.

Each vine has three significant attributes:

  • anchorPoint: a CGPoint indicating where the end of the vine connects to the tree
  • length: an Int representing the number of segments in the vine
  • name: a String used to identify which vine a given segment belongs to

In this tutorial, the game has only one level. But in a real game you would want to be able to easily create new level layouts without writing a lot of code. A good way to do this is to specify your level data independently of your game logic, perhaps by storing it in a data file with a property list or JSON.

Since you’ll be loading your vine data from a file, the natural structure for representing the vine data is an NSArray of NSDictionary objects, which can be easily read from a property list using the NSArray(contentsOfFile:) initializer. Each dictionary will represent one vine.

In GameScene.swift, locate setUpVines() and add the following code:

// 1 load vine data
let dataFile = Bundle.main.path(forResource: GameConfiguration.VineDataFile, ofType: nil)
let vines = NSArray(contentsOfFile: dataFile!) as! [NSDictionary]
    
// 2 add vines
for i in 0..<vines.count {
  // 3 create vine
  let vineData = vines[i]
  let length = Int(vineData["length"] as! NSNumber)
  let relAnchorPoint = CGPointFromString(vineData["relAnchorPoint"] as! String)
  let anchorPoint = CGPoint(x: relAnchorPoint.x * size.width,
                            y: relAnchorPoint.y * size.height)
  let vine = VineNode(length: length, anchorPoint: anchorPoint, name: "\(i)")
  
  // 4 add to scene
  vine.addToScene(self)
  
  // 5 connect the other end of the vine to the prize
  vine.attachToPrize(prize)
}

With this code, you:

  1. Load the vine data from a property list file. Take a look at the VineData.plist file inside Resources/Data, and you should see that the file contains an array of dictionaries, each containing a relAnchorPoint and length:

    snipVinePlist

  2. The for loop iterates over the indexes in the array. The reason for iterating over the indexes instead of just the array objects is that you need the index value in order to generate a unique name string for each vine. This will be important later.
  3. For each vine dictionary, the length and relAnchorPoint are retrieved and used to initialize a new VineNode object. length specifies the number of segments in the vine. relAnchorPoint is used to determine the anchor position of the vine, relative to the size of the scene.
  4. Finally, you attach the VineNode to the scene with addToScene().
  5. Then attach it to the prize using attachToPrize().

Next you'll implement those methods in VineNode.

Defining the Vine Class

Open VineNode.swift. VineNode is a custom class that inherits from SKNode. It doesn't have any visual appearance of its own, but instead acts as a container for a collection of SKSpriteNodes representing the vine segments.

Add the following properties to the class definition:

private let length: Int
private let anchorPoint: CGPoint
private var vineSegments: [SKNode] = []

You'll see some errors appearing because the length and anchorPoint properties haven't been initialized. You've declared them as non-optional, but not assigned a value. Fix this by replacing the implementation of the init(length:anchorPoint:name:) method with the following:

self.length = length
self.anchorPoint = anchorPoint
  
super.init()
    
self.name = name

Pretty straightforward, but for some reason there are still errors. There is a second initializer method, init(coder:) — you aren't calling that anywhere, so what is it for?

Because SKNode implements the NSCoding protocol, it inherits the required initializer init(coder:), and that means you have to initialize your non-optional properties there as well, even though you aren't using it.

Do that now. Replace the content of init(coder:) with:

length = aDecoder.decodeInteger(forKey: "length")
anchorPoint = aDecoder.decodeCGPoint(forKey: "anchorPoint")
    
super.init(coder: aDecoder)

Next, you need to implement the addToScene() method. This is a complex method, so you'll write it in stages. First, find addToScene() and add the following:

// add vine to scene
zPosition = Layer.Vine
scene.addChild(self)

You add the vine to the scene and sets its zPosition. Next, add this block of code to same the method:

// create vine holder
let vineHolder = SKSpriteNode(imageNamed: ImageName.VineHolder)
vineHolder.position = anchorPoint
vineHolder.zPosition = 1

addChild(vineHolder)

vineHolder.physicsBody = SKPhysicsBody(circleOfRadius: vineHolder.size.width / 2)
vineHolder.physicsBody?.isDynamic = false
vineHolder.physicsBody?.categoryBitMask = PhysicsCategory.VineHolder
vineHolder.physicsBody?.collisionBitMask = 0

This creates the vine holder, which is like a nail for the vine to hang from. As with the crocodile, this body is not dynamic and does not collide with other bodies.

The vine holder is circular, so use the SKPhysicsBody(circleOfRadius:) constructor. The position of the vine holder matches the anchorPoint that you specified when creating the VineModel.

Next, you'll create the vine. Add the following code, again to the bottom of the same method:

// add each of the vine parts
for i in 0..<length {
  let vineSegment = SKSpriteNode(imageNamed: ImageName.VineTexture)
  let offset = vineSegment.size.height * CGFloat(i + 1)
  vineSegment.position = CGPoint(x: anchorPoint.x, y: anchorPoint.y - offset)
  vineSegment.name = name

  vineSegments.append(vineSegment)
  addChild(vineSegment)

  vineSegment.physicsBody = SKPhysicsBody(rectangleOf: vineSegment.size)
  vineSegment.physicsBody?.categoryBitMask = PhysicsCategory.Vine
  vineSegment.physicsBody?.collisionBitMask = PhysicsCategory.VineHolder
}

This loop creates an array of vine segments, equal in number to the length you specified when creating the VineModel. Each segment is a sprite with its own physics body. The segments are rectangular, so you use SKPhysicsBody(rectangleOfSize:) to specify the shape of the physics body.

Unlike the vine holder, the vine nodes are dynamic, so they can move around and are affected by gravity.

Build and run the app to see your progress.

Uh oh! The vine segments fall off the screen like chopped spaghetti!

spaghetti

Adding Joints to the Vines

The problem is that you haven't joined the vine segments together yet. To fix that, you need to add this final chunk of code to the bottom of addToScene():

// set up joint for vine holder
let joint = SKPhysicsJointPin.joint(withBodyA: vineHolder.physicsBody!,
                                    bodyB: vineSegments[0].physicsBody!,
                                    anchor: CGPoint(x: vineHolder.frame.midX, y: vineHolder.frame.midY))
scene.physicsWorld.add(joint)

// set up joints between vine parts
for i in 1..<length {
  let nodeA = vineSegments[i - 1]
  let nodeB = vineSegments[i]
  let joint = SKPhysicsJointPin.joint(withBodyA: nodeA.physicsBody!, bodyB: nodeB.physicsBody!,
                                      anchor: CGPoint(x: nodeA.frame.midX, y: nodeA.frame.minY))

  scene.physicsWorld.add(joint)
}

This code sets up physical joints between the segments, connecting them together. The type of joint you've used is an SKPhysicsJointPin, which behaves as if you had hammered a pin through the two nodes, allowing them to pivot around the pin, but not move closer or further apart from one another.

Build and run again. Your vines should hang realistically from the trees.

Screen3

The final step is to attach the vines to the pineapple. Still in VineNode.swift, scroll to attachToPrize(). Add the following code:

// align last segment of vine with prize
let lastNode = vineSegments.last!
lastNode.position = CGPoint(x: prize.position.x, y: prize.position.y + prize.size.height * 0.1)
    
// set up connecting joint
let joint = SKPhysicsJointPin.joint(withBodyA: lastNode.physicsBody!, 
                                    bodyB: prize.physicsBody!, anchor: lastNode.position)
    
prize.scene?.physicsWorld.add(joint)

This code gets the last segment of the vine and positions it slightly above the center of the prize. (We want to attach it here so the prize hangs down realistically. If it was dead-center, the prize would be evenly weighted, and might spin on its axis.) It also creates another pin joint to attach the vine segment to the prize.

Build and run the project. If all your joints and nodes are set up properly, you should see a screen similar to the one below:

Screen4

Yay! A dangling pineapple - who the heck ties pineapples to trees? :]

Snipping the Vines

You probably noticed that you still can't snip those vines? Let's sort that little problem out next.

In this section, you’ll work with touch methods that will allow players to snip those hanging vines. Back in GameScene.swift, locate touchesMoved() and add the following code:

for touch in touches {
  let startPoint = touch.location(in: self)
  let endPoint = touch.previousLocation(in: self)
  
  // check if vine cut
  scene?.physicsWorld.enumerateBodies(alongRayStart: startPoint, end: endPoint,
                                      using: { (body, point, normal, stop) in
    self.checkIfVineCutWithBody(body)
  })
  
  // produce some nice particles
  showMoveParticles(touchPosition: startPoint)
}

This code works as follows: For each touch it gets the current and previous positions of the touch. Next, it loops through all of the bodes in the scene that lie between those two points, using the very handy enumerateBodies(alongRayStart:end:using:) method of SKScene. For each body encountered it calls checkIfVineCutWithBody(), which you'll write in a minute.

Finally, the code calls a method that creates an SKEmitterNode by loading it from the Particle.sks file, and adds it to the scene at the position of the user's touch. This results in a nice green smoke trail wherever you drag your finger (pure eye candy!)

Scroll down to the checkIfVineCutWithBody() method, and add this block of code to the method body:

let node = body.node!

// if it has a name it must be a vine node
if let name = node.name {
  // snip the vine
  node.removeFromParent()

  // fade out all nodes matching name
  enumerateChildNodes(withName: name, using: { (node, stop) in
    let fadeAway = SKAction.fadeOut(withDuration: 0.25)
    let removeNode = SKAction.removeFromParent()
    let sequence = SKAction.sequence([fadeAway, removeNode])
    node.run(sequence)
  })
}

The code above first checks if the node connected to the physics body has a name. Remember that there are other nodes in the scene besides vine segments, and you certainly don't want to accidentally slice up the crocodile or the pineapple with a careless swing! But you only named the vine node segments, so if the node has a name then you can be certain that it's part of a vine.

Next, you remove the node from the scene. Removing a node also removes its physicsBody, and destroys any joints connected to it. The vine has now officially been snipped!

Finally, you enumerate through all nodes in the scene whose name matches the name of the node that was swiped, using the scene's enumerateChildNodes(withName:using:). The only nodes whose name should match are the other segments in the same vine, so you are basically just looping over the segments of whichever vine was sliced.

For each node, you create an SKAction sequence that first fades out the node and then removes it from the scene. The effect is that after being sliced, each vine will fade away.

Build and run the project. Try and snip those vines - you should now be able to swipe and cut all three vines and see that prize fall. Sweet pineapples! :]

Screen5

Handling Contact Between Bodies

When you wrote the setUpPhysics() method, you specified that GameScene would act as the contactDelegate for the physicsWorld. You also configured the contactTestBitMask of the croc so that SpriteKit would notify when it intersects with the prize. That was excellent foresight!

Now you need to implement didBegin() of SKPhysicsContactDelegate, which will be triggered whenever an intersection is detected between two appropriately masked bodies. A stub for that method has been added for you — scroll down to find it, and add the following code:

if (contact.bodyA.node == crocodile && contact.bodyB.node == prize)
  || (contact.bodyA.node == prize && contact.bodyB.node == crocodile) {
  
  // shrink the pineapple away
  let shrink = SKAction.scale(to: 0, duration: 0.08)
  let removeNode = SKAction.removeFromParent()
  let sequence = SKAction.sequence([shrink, removeNode])
  prize.run(sequence)
}

This code checks if the two intersecting bodies belong to the crocodile and prize (you don't know which order the nodes will be listed in, so you check for both combinations). If the test passes, you trigger a simple animation sequence that shrinks the prize down to nothing and then removes it from the scene.

Animate the Crocodile Chomp

You want the crocodile to chomp down when she catches a pineapple. Inside the if statement where you just triggered the pineapple shrink animation, add the following extra line:

runNomNomAnimationWithDelay(0.15)

Now locate runNomNomAnimationWithDelay() and add this code:

crocodile.removeAllActions()

let closeMouth = SKAction.setTexture(SKTexture(imageNamed: ImageName.CrocMouthClosed))
let wait = SKAction.wait(forDuration: delay)
let openMouth = SKAction.setTexture(SKTexture(imageNamed: ImageName.CrocMouthOpen))
let sequence = SKAction.sequence([closeMouth, wait, openMouth, wait, closeMouth])

crocodile.run(sequence)

The code above removes any animation currently running on the crocodile node using removeAllActions(). It then creates a new animation sequence that closes and opens the crocodile’s mouth and has the crocodile run this sequence.

This new animation will be triggered when the prize lands in the croc's mouth, giving the impression that the crocodile is chewing it.

While you're at it, add the following lines inside the if statement of checkIfVineCutWithBody():

crocodile.removeAllActions()
crocodile.texture = SKTexture(imageNamed: ImageName.CrocMouthOpen)
animateCrocodile()

This will ensure that the croc's mouth is open when you snip a vine and there's a chance of the prize falling in the croc's mouth.

Build and run.

Resetting the Game

The happy croc will now chew up the pineapple if it lands in her mouth. But once that's happened, the game just hangs there.

In GameScene.swift, find switchToNewGameWithTransition(), and add the following code:

let delay = SKAction.wait(forDuration: 1)
let sceneChange = SKAction.run({
  let scene = GameScene(size: self.size)
  self.view?.presentScene(scene, transition: transition)
})
    
run(SKAction.sequence([delay, sceneChange]))

The code above uses SKView’s presentScene(_:transition:) method to present the next scene.

In this case, the scene you are transitioning to is a new instance of the same GameScene class. You also pass in a transition effect using the SKTransition class. The transition is specified as an argument to the method so that you can use different transition effects depending on the outcome of the game.

Scroll back to didBegin(), and inside the if statement, after the prize shrink and nomnom animations, add the following:

// transition to next level
switchToNewGameWithTransition(SKTransition.doorway(withDuration: 1.0))

This calls the switchToNewGameWithTransition() method using the SKTransition.doorway(withDuration:) initializer to create a doorway transition. This shows the next level with an effect like a door opening. Pretty neat, huh?

Screen6

Ending the Game

You might think that you need to add another physics body to the water so you can detect if the prize hits it, but that wouldn't really help if the pineapple were to fly off the side of the screen. A simpler, better approach is just to detect when the pineapple has moved below the bottom of the screen edge, and then end the game.

SKScene provides an update() method that is called once every frame. Find that method, and add the following logic:

if prize.position.y <= 0 {
  switchToNewGameWithTransition(SKTransition.fade(withDuration: 1.0))
}

The if statement checks if the prize's y coordinate is less than zero (the bottom of the screen). If so, it calls switchToNewGameWithTransition() to start the level again, this time using a SKTransition.fade(withDuration:).

Build and run the project.

You should see the scene transition to a new scene whenever the player wins or loses.

Adding Sound and Music

We’ve selected a nice jungle song from incompetech.com and some sound effects from freesound.org.

SpriteKit will handle the sound effects for you. But you'll use AVAudioPlayer to play the background music seamlessly between level transitions.

Add another property to GameScene.swift:

private static var backgroundMusicPlayer: AVAudioPlayer!

This declares a type property so all instances of GameScene will be able to access the same backgroundMusicPlayer. Locate the setUpAudio() method and add the following code:

if GameScene.backgroundMusicPlayer == nil {
  let backgroundMusicURL = Bundle.main.url(forResource: SoundFile.BackgroundMusic, withExtension: nil)
  
  do {
    let theme = try AVAudioPlayer(contentsOf: backgroundMusicURL!)
    GameScene.backgroundMusicPlayer = theme

  } catch {
    // couldn't load file :[
  }
  
  GameScene.backgroundMusicPlayer.numberOfLoops = -1
}

The code above checks if the backgroundMusicPlayer has been created yet. If not, it initializes a new AVAudioPlayer with the BackgroundMusic constant that you added to Constants.swift earlier (which is converted to a URL), and assigns it to the property. The numberOfLoops vale is set to -1, which indicates that the song should loop indefinitely.

Next, add this code, to the bottom of the setUpAudio() method:

if !GameScene.backgroundMusicPlayer.isPlaying {
  GameScene.backgroundMusicPlayer.play()
}

This starts the background music playing when the scene is first loaded (it will then play indefinitely until the app exits or another method calls stop() on the player). You could just call play() without first checking if the player is playing, but this way the music won't skip or restart if it is already playing when the level begins.

While you're here, you may as well set up all the sound effects that you will be using later. Unlike the music, you don't want to play the sound effects right away. Instead, you'll create some reusable SKActions that can be used to play the sounds later.

Go back up to the top of the GameScene class definition, and add the following properties:

private var sliceSoundAction: SKAction!
private var splashSoundAction: SKAction!
private var nomNomSoundAction: SKAction!

Now go back to setUpAudio() and add the following lines to the bottom of the method:

sliceSoundAction = SKAction.playSoundFileNamed(SoundFile.Slice, waitForCompletion: false)
splashSoundAction = SKAction.playSoundFileNamed(SoundFile.Splash, waitForCompletion: false)
nomNomSoundAction = SKAction.playSoundFileNamed(SoundFile.NomNom, waitForCompletion: false)

This code initializes the sound actions using SKAction’s playSoundFileNamed(_:waitForCompletion:) method. Now it's time to actually play the sound effects.

Scroll up to update() and add the following line of code inside the if statement above the call to switchToNewGameWithTransition():

run(splashSoundAction)

That will play the sound of a splash when the pineapple lands in the water. Next, find didBegin() and add the following line of code just below the runNomNomAnimationWithDelay(0.15) line:

run(nomNomSoundAction)

That will play a chomping sound when the croc catches her prize. Finally, locate checkIfVineCutWithBody() and add the following line of code inside the if statement:

run(sliceSoundAction)

That will play a swiping sound whenever the player snips a vine.

Build and run the project.

Did you discover a bug? If you miss the croc, the splashing sound plays multiple times. This is due to "level complete" logic being triggered repeatedly before the game transitions to the next scene. To correct this, add a new state property to the top of the class:

private var levelOver = false

Now modify update() and didBegin() by adding the following code at the top of each of them:

if levelOver {
  return
}

Finally, inside the other if statements of the same methods, add some code to set the levelOver state to true:

levelOver = true

Now, as soon as the game detects that the levelOver flag has been set (either because the pineapple hit the ground, or the croc got her meal), it will stop checking for the game win/lose scenarios, and won't keep repeatedly trying to play those sound effects. Build and run. There are no awkward sound effects anymore!

FeedingTime

Adding Haptic Feedback

The iPhone 7 features a new taptic engine to provide touch feedback to users. It's most famous for the simulated "click" on the phone's new home button (which has no moving parts.) But developers can also tap into it with a few lines of code thanks to the UIFeedbackGenerator class.

You'll use the UIImpactFeedbackGenerator subclass to add a little shimmy to your sprite collisions. This class comes with three settings: light, medium and heavy. When our croc chomps down on the pineapple, you'll add a heavy impact. When the pineapple flies offscreen, you'll add a light impact.

First, instantiate the feedback generators. In GameScene.swift, add the following properties just before didMove():

let chomp = UIImpactFeedbackGenerator(style: .heavy)
let splash = UIImpactFeedbackGenerator(style: .light)  

Next, trigger the feedback with the impactOccurred() method.

Scroll to update() and directly below the run(splashSoundAction) line, add the following:

splash.impactOccurred()

Next, find didBegin() and below the run(nomNomSoundAction) line, add the following:

chomp.impactOccurred()

Build and run your game on an iPhone 7 to feel the haptic feedback.

If you want to learn more about haptic feedback, check out this short video tutorial, iOS 10: Providing Haptic Feedback.

Adding Some Difficulty

After playing a few rounds, the game seems a bit on the easy side. You'll pretty quickly get to the point where you can feed the croc with a single well-timed slice through the three vines.

Let's make things trickier by using that constant you set earlier, CanCutMultipleVinesAtOnce.

In GameScene.swift, add one last property at the top of the GameScene class definition:

private var vineCut = false

Now locate the checkIfVineCutWithBody() method, and add the following if statement at the top of the method:

if vineCut && !GameConfiguration.CanCutMultipleVinesAtOnce {
  return
}

Add this line of code to the bottom of the same method:

vineCut = true

Just to keep things together, find touchesMoved(), and add this method above it:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
  vineCut = false
}

This way you reset the vineCut flag whenever the user touches the screen.

Build and run the game again. You should see now that it is only possible to snip one vine each time you swipe. To cut another, you have to lift your finger and then swipe again.

Where To Go From Here?

Download the completed sample project here.

But don't stop there! Try adding new levels, different vines, and maybe even a HUD with a score display and timer.

If you’d like 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:

Kevin Colligan

Kevin is a long-time digital media pro and newer iOS game developer. He has held technical and content leadership roles with the Grammy Awards, Major League Baseball, Acquia and Fox Sports. See kevcolLabs.com for more on his games or LinkedIn for his professional bona fides.

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

... 43 total!

Android Team

... 14 total!

macOS Team

... 11 total!

Unity Team

... 11 total!

Articles Team

... 12 total!

Resident Authors Team

... 15 total!