How To Make a Game Like Cut the Rope With SpriteKit

In this tutorial, you’ll learn how to build a game like Cut the Rope with SpriteKit in Swift, complete with animations, sound effects and physics! By Brody Eller.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Handling Contact Between Bodies

When you wrote setUpPhysics(), 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, in GameScene.swift you need to implement didBegin(_:) of SKPhysicsContactDelegate, which will trigger whenever it detects an intersection between two appropriately-masked bodies. There's a stub for that method — 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 the prize. You don't know the nodes' order, 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's Chomp

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

runNomNomAnimation(withDelay: 0.15)

Now locate runNomNomAnimation(withDelay:) 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 trigger 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 below the call to enumerateChildNodes in checkIfVineCut(withBody:):

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

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

Build and run.

Cut the rope with SpriteKit tutorial crocodile animation

The happy croc will now chew up the pineapple if it lands in its mouth. But once that's happened, the game just hangs there. You're solve that issue next.

Resetting the Game

Next, you'll reset the game when the pineapple falls down or the croc eats the pineapple.

In GameScene.swift, find switchToNewGame(withTransition:), 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(.sequence([delay, sceneChange]))

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

In this case, the scene you transition to is a new instance of the same GameScene. You also pass in a transition effect using the SKTransition class. You specify the transition 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
switchToNewGame(withTransition: .doorway(withDuration: 1.0))

This calls switchToNewGame(withTransition:) using the .doorway(withDuration:) initializer to create a doorway transition. This shows the next level with an effect like a door opening. Build and run to see the effect.

Cut the rope with SpriteKit tutorial scene transition

Pretty neat, huh?

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 help if the pineapple flies 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, then end the game.

SKScene provides update(_:) that's called once every frame. Find that method in GameScene.swift and add the following logic:

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

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

Build and run.

Cut the rope with SpriteKit fade out scene transition

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

Adding Sound and Music

Now, you're going to add 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.

Adding the Background Music

To start adding the music, 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 setUpAudio() 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 exists yet. If not, it initializes a new AVAudioPlayer with the BackgroundMusic that you added to Constants.swift earlier. It then converts it to a URL and assigns it to the property. The numberOfLoops value is set to -1, which indicates that the song should loop indefinitely.

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

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

This starts the background music when the scene first loads. 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's already playing when the level begins.

Adding the Sound Effects

While you're here, you may as well set up all the sound effects that you'll use later. Unlike the music, you don't want to play the sound effects right away. Instead, you'll create some reusable SKActions that will 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 = .playSoundFileNamed(
  SoundFile.slice,
  waitForCompletion: false)
splashSoundAction = .playSoundFileNamed(
  SoundFile.splash,
  waitForCompletion: false)
nomNomSoundAction = .playSoundFileNamed(
  SoundFile.nomNom,
  waitForCompletion: false)

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

Scroll up to update(_:) and add the following code inside the if statement above the call to switchToNewGame(withTransition:):

run(splashSoundAction)

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

run(nomNomSoundAction)

That will play a chomping sound when the croc catches its prize. Finally, locate checkIfVineCut(withBody:) and add the following code at the bottom of the if let statement:

run(sliceSoundAction)

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

Build and run to enjoy that crunchy sound when the croc eats the pineapple!