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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How To Make a Game Like Cut the Rope With SpriteKit
30 mins
- Getting Started
- Adding the Crocodile to the Scene
- Adding the Prize
- Working With Physics
- Adding Vines
- Defining the Vine Class
- Adding Joints to the Vines
- Snipping the Vines
- Handling Contact Between Bodies
- Animate the Crocodile's Chomp
- Resetting the Game
- Ending the Game
- Adding Sound and Music
- Adding the Background Music
- Adding the Sound Effects
- Getting Rid of an Awkward Sound Effect
- Adding Some Difficulty
- Where to Go From Here?
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.
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.
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.
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 SKAction
s 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!