How to Make a Line Drawing Game with Sprite Kit and Swift
Learn how to make a Line Drawing Game like Flight Control with Sprite Kit and Swift! By Jean-Pierre Distler.
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 Line Drawing Game with Sprite Kit and Swift
55 mins
- Getting Started
- Adding the Background… and a Pig!
- Moving the Sprite
- Responding to Touches
- Drawing Lines
- Continuous Movement
- Rotating the Sprite
- Animating the Sprite
- Your Gameplay Strategy
- Completing the Scene
- Spawning Pigs
- Detecting Collisions
- Adding Physics Bodies
- Feeding the Pigs
- Finishing the Game
- Game Over: When Pigs Collide
- Adding Polish
- Where to Go From Here?
Completing the Scene
To complete the first step, open GameScene.swift and add the following properties:
var homeNode = SKNode()
var currentSpawnTime: NSTimeInterval = 5.0
homeNode
will display an image of a barn and act as the final goal for your pigs. You'll use currentSpawnTime
to track the wait time between pig spawns. After each new pig appears, you'll reduce this value to speed up the next spawn.
Then add this new method to set up the sprites in your scene:
func loadLevel () {
//1
let bg = SKSpriteNode(imageNamed:"bg_2_grassy")
bg.anchorPoint = CGPoint(x: 0, y: 0)
addChild(bg)
//2
let foodNode = SKSpriteNode(imageNamed:"trough_3_full")
foodNode.name = "food"
foodNode.position = CGPoint(x:250, y:200)
foodNode.zPosition = 1
// More code later
addChild(foodNode)
//3
homeNode = SKSpriteNode(imageNamed: "barn")
homeNode.name = "home"
homeNode.position = CGPoint(x: 500, y: 20)
homeNode.zPosition = 1
addChild(homeNode)
currentSpawnTime = 5.0
}
Let’s take a tour of this code:
- This is simply the code to create the background that you already have in
init(size:)
. You'll remove it from that method soon. - Here you create an
SKSpriteNode
for the trough and give it the name "food". You place the node near the center of the screen and add it to the scene. - This creates the barn and positions it in the lower-right corner of your scene. Finally, you set the current spawn time to five seconds. You could make the game easier or harder by increasing or decreasing the initial spawn time.
Now replace all of the code, except the call to super.init(size: size)
within init(size:)
with the following call to loadLevel()
:
loadLevel()
Build and run, and you'll see the stage is now set:
But no pigs are anywhere to be found! Let's bring in some grunts.
Spawning Pigs
To spawn some pigs, add the following new method to GameScene.swift:
func spawnAnimal() {
//1
currentSpawnTime -= 0.2
//2
if currentSpawnTime < 1.0 {
currentSpawnTime = 1.0
}
let pig = Pig(imageNamed: "pig_1")
pig.position = CGPoint(x: 20, y: Int(arc4random_uniform(300)))
pig.name = "pig"
addChild(pig)
runAction(SKAction.sequence([SKAction.waitForDuration(currentSpawnTime), SKAction.runBlock({
self.spawnAnimal()
}
)]))
}
Here’s a step-by-step breakdown of the code above:
- This decreases the time between spawns by 0.2 seconds every time the game spawns a pig.
- You ensure the spawn time never falls below one second, because anything faster than that would make the game too difficult, and if it hit zero, things would probably break.
- Here you create a pig and add it to the scene like you did before in
init(size:)
. Now you set the pig’s position with a fixed x-value of 20 and a random y-value that ranges between zero and 299. Setting the pig’szPosition
to 1 makes sure the pig renders on top of the lines in the scene, which you’ve added with the defaultzPosition
of zero. - This runs an action sequence. A sequence performs a set of actions in order, one at a time. The result is that the
performSelector
action callsspawnAnimal
again afterwaitForDuration
waits forcurrentSpawnTime
seconds. Because you reducecurrentSpawnTime
each time you call this method, you end up callingspawnAnimal
with less and less of a delay.
Now add a call to your method in init(size:)
, immediately after the call to loadLevel()
:
spawnAnimal()
Build and run, and watch the pigs appear faster and faster over time.
Detecting Collisions
As you can see in the image above, the pigs move through the trough, barn and even other pigs.
This is a sign your game needs collision detection! Fortunately, you don't have to create it from scratch. Sprite Kit includes a physics engine that you can use for collision detection.
First, add some physics categories to your game. Open GameScene.swift and add the following enum
above the class
part:
enum ColliderType: UInt32 {
case Animal = 1
case Food = 2
}
This creates two categories, one for each type of physics body you will have. You can use these categories to detect collisions between different physics bodies. There are two types of collisions that can occur in this game, those between two pigs and those between a pig and the food trough.
Now your scene needs to be the delegate for the physics engine and therefore needs to implement the
SKPhysicsContactDelegate protocol. Change your class definition to:
class GameScene: SKScene, SKPhysicsContactDelegate {
This tells the compiler that your scene is implementing the SKPhysicsContactDelegate protocol.
Now find init(size:)
and add this right before the call to loadLevel()
:
physicsWorld.gravity = CGVectorMake(0.0, 0.0)
physicsWorld.contactDelegate = self
This configures your physics world. The first line disables gravity in your scene and the second registers your scene as the contact delegate of the physics world. Sprite Kit notifies this delegate whenever two appropriately configured physics bodies begin to touch or stop touching.
To process these collision events, add the following method to the scene:
func didBeginContact(contact: SKPhysicsContact) {
//1
let firstNode = contact.bodyA.node
let secondNode = contact.bodyB.node
//2
let collision = firstNode!.physicsBody!.categoryBitMask | secondNode!.physicsBody!.categoryBitMask
//3
if collision == ColliderType.Animal.rawValue | ColliderType.Animal.rawValue {
NSLog("Animal collision detected")
} else if collision == ColliderType.Animal.rawValue | ColliderType.Food.rawValue {
NSLog("Food collision detected.")
} else {
NSLog("Error: Unknown collision category \(collision)")
}
}
Let’s break down what’s happening above:
- These two lines give you the nodes that just collided. There is no specific order for the nodes, so you have to check the objects yourself if you care which is which.
- You perform a bitwise-OR of the categories of the two collided nodes and store it in
collision
. - Here you figure out what kind of collision occurred by comparing
collision
with the bit mask for an animal/animal or animal/food collision. For the moment, you simply log the detected collision to the console.
There’s something essential missing, though. Can you guess what it is?