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?
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 SKSpriteNode
s that represent the vine segments.
Add the following properties to the class definition:
private let length: Int
private let anchorPoint: CGPoint
private var vineSegments: [SKNode] = []
Some errors will appear because you haven’t initialized the length
and anchorPoint
properties. You’ve declared them as non-optional, but you haven’t assigned a value. Fix this by replacing the implementation of init(length:anchorPoint:name:)
with the following:
self.length = length
self.anchorPoint = anchorPoint
super.init()
self.name = name
Pretty straightforward… but for some reason, there are still errors. You might notice there’s a second initializer method, init(coder:)
. You aren’t calling it anywhere, so what is it for?
Because SKNode
implements NSCoding
, it inherits the required initializer init(coder:)
. 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 addToScene(_:)
. 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 set 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:)
initializer. The position of the vine holder matches the anchorPoint
that you specified when creating the VineNode
.
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 that's equal in number to the length you specified when you created VineNode
. Each segment is a sprite with its own physics body. The segments are rectangular, so you use SKPhysicsBody(rectangleOf:)
to specify the shape of the physics body.
Unlike the vine holder, the vine nodes are dynamic – they can move around and are affected by gravity.
Build and run to see your progress.
Uh oh! The vine segments fall off the screen like chopped 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
. This joint type behaves as if you'd hammered a pin through the two nodes, allowing them to pivot around the pin but not move closer or farther from one another.
Build and run again. Your vines should hang realistically from the trees.
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. You 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. If all your joints and nodes are set up properly, you should see a screen similar to the one below:
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! You'll sort out that little problem 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(_:with:)
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, _, _, _ in
self.checkIfVineCut(withBody: body)
})
// produce some nice particles
showMoveParticles(touchPosition: startPoint)
}
This code works as follows: First, it gets the current and previous positions of each 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 checkIfVineCut(withBody:)
, which you'll write in a minute.
Finally, the code calls a method that creates an SKEmitterNode
by loading it from Particle.sks, 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!
Now, scroll down to checkIfVineCut(withBody:)
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, _ 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 that's connected to the physics body has a name. Remember, 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, 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. You've now officially snipped the vine!
Finally, you enumerate through all nodes in the scene whose name matches the name of the node that you 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're just looping over the segments of whichever vine you sliced.
For each node, you create an SKAction
that first fades out the node and then removes it from the scene. The effect is that after it's sliced, each vine will fade away.
Build and run. Try and snip those vines – you should now be able to swipe and cut all three vines and see that prize fall. Sweet pineapples! :]