Home iOS & Swift Tutorials

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!


  • Other, Other, Other
Learn how to make a line drawing game like Flight Control!

Learn how to make a line drawing game like Flight Control!

Note from Ray: This is a Swift update to a popular Objective-C tutorial on our site, released as part of the iOS 8 Feast. Enjoy!

Update 12/09/2014
Updated for Xcode 6.1.1.

In 2009, Firemint introduced the line drawing game to the world when they released the incredibly popular Flight Control.

In a line drawing game, you trace a line with your finger and then sprites follow the line that you drew.

In this tutorial, you’ll learn how to write your own line drawing game with Sprite Kit. Instead of being a mere clone of Flight Control, you will create a game called “Hogville” where your goal is to bring some cute and tired pigs to food and shelter.

This Swift tutorial assumes you have working knowledge of Sprite Kit and Swift. If you’re new to Sprite Kit, check out the beginner tutorials on the site or the book, iOS Games by Tutorials. For an introduction to Swift, check out this beginner Swift tutorial here.

Getting Started

To get started, download the starter project.

I created this starter project using the Sprite Kit Game template and set it to run in landscape mode. I removed GameScene.sks, changed GameViewController‘s viewDidLoad method to create the game scene (without using the SKS file) and add it to the view. I also added all the artwork you’ll need – and a big thanks to Vicki Wenderlich for providing the art!

Build and run, and you should see a blank screen in landscape as a starting point:


Now you can get right to the business of adding your game elements and developing the line drawing gameplay.

Adding the Background… and a Pig!

After a long day of being a pig, all you want is some food and a bed—a pile of hay will do! It’s hard work rolling in the mud all day. In Hogville, it will be your player’s job to give the pigs what they want by drawing the lines to steer them home.

Before you start drawing lines, though, you need a pig to follow them. Your pig would be a bit unhappy floating in a black void, so you’ll also add a background to give the poor pig some familiar surroundings.

Open GameScene.swift and add the following methods:

override init(size: CGSize) {
  super.init(size: size)

  let bg = SKSpriteNode(imageNamed: "bg_2_grassy")
  bg.anchorPoint = CGPoint(x: 0, y: 0)
  let pig = SKSpriteNode(imageNamed: "pig_1")
  pig.position = CGPoint(x: size.width / 2, y: size.height / 2)

required init(coder aDecoder: NSCoder) {
  fatalError("init(coder:) has not been implemented")

Here you override init(size:) to add the background image and a pig sprite. You place the lower-left corner of the background image in the lower-left corner of the scene by setting bg‘s anchorPoint to (0, 0) and using its default position.

You also implement the required init(coder:) method, although only with a dummy implementation because you won’t need it in this game.

Build and run your game and behold your plump, rosy pig in the middle of a sunny, green field.


Moving the Sprite

Next you need to create a class for the pig sprite. This will contain the path which the pig should follow, along with methods to make the pig follow this path over time.

To create a class for the pig sprite, go to File\New\File…, choose the Swift File template and click Next. Name the file Pig.swift and then click Create.

In Pig.swift add:

import SpriteKit

class Pig: SKSpriteNode {


First you need to import SpriteKit then you create the class Pig as a subclass of SKSpriteNode. Now add the following init methods to the Pig class:

init(imageNamed name: String) {
  let texture = SKTexture(imageNamed: name)
  super.init(texture: texture, color: nil, size: texture.size())
required init(coder aDecoder: NSCoder) {
  super.init(coder: aDecoder)

In init(imageNamed:) you create a texture and then call super.init(texture:, color:, size:)
Now add a constant to your class. Add the following right after the line class Pig: SKSpriteNode:

let POINTS_PER_SEC: CGFloat = 80.0

This constant defines the speed of the pig as 80 points per second.

Note: You set the type of POINTS_PER_SEC explicitly to CGFloat. If you use Swifts type inference
POINTS_PER_SEC would be a Double. Many SpriteKit and also UIKit methods takes CGFloats as an argument, but Swift’s type safety avoids implicit type conversion, so you have to cast Swift’s Double and Float to CGFloat.

Unfortunately type casts aren’t that good for readability and so I decided to have one weird line of Swift code but less casts needed.

Next, declare two more properties by adding the following code right below POINTS_PER_SEC:

var wayPoints: [CGPoint] = []
var velocity = CGPoint(x: 0, y: 0)

wayPoints will do what its name suggests and store all the points along which the pig should move. velocity will store the pig’s current speed and direction.

Now you need a method to add waypoints to the pig. Add the method addMovingPoint() like this:

func addMovingPoint(point: CGPoint) {

This method simply adds the given point to the wayPoints array.

Now add another method move(). You will add more code later but for the moment add it like shown below:

func move(dt: NSTimeInterval) {
  let currentPosition = position 
  var newPosition = position
  if wayPoints.count > 0 {
    let targetPoint = wayPoints[0]

    //2 TODO: Add movement logic here

    if frame.contains(targetPoint) {

You will call this method each frame to move the pig a little bit along its path. Here’s how this part of the method works:

  1. First you check to ensure there are waypoints left in the array. For the moment, the pig stops moving when it reaches the final point of the path. Later, you’ll make the pig a little smarter so it continues walking in its last direction even when no waypoints remain.
  2. This comment marks where you’ll put the code that updates the pig’s position. You’ll add that code next.
  3. Finally, you check if the pig has reached the waypoint by seeing if the pig’s frame contains the targetPoint. In this case, you remove the point from the array so that your next call to move will use the next point. Note that it’s important to check if the frame contains the target point (rather than checking if the position equals the target point), effectively stopping the pig when he’s “close enough”. That makes some of the calculations later a bit easier.

You added that final if statement in the above code because the pig isn’t guaranteed to reach the waypoint in just one call to move(). That makes sense, because the pig needs to move at a constant speed, a little each frame.

Why? Let’s assume you have the first waypoint in the upper-left corner at (0, 50) and the second point at (300, 50). Something like this can happen if the player moves their finger very fast over the screen.

If you took the simple approach of setting the position to the first point in the array and then to the second point in the array, your pig would appear to teleport from one waypoint to the next. Have you ever seen a teleporting pig? I’m sure even Captain Kirk can’t make that claim.

Beam me up, piggy!

With the logic to process the waypoints in place, it’s time to add the code that calculates and updates the pig’s new position along the path between the waypoints. In move(), replace the //2 TODO: Add movement logic here comment with the following code:

let offset = CGPoint(x: targetPoint.x - currentPosition.x, y: targetPoint.y - currentPosition.y)
let length = Double(sqrtf(Float(offset.x * offset.x) + Float(offset.y * offset.y)))
let direction = CGPoint(x:CGFloat(offset.x) / CGFloat(length), y: CGFloat(offset.y) / CGFloat(length))
velocity = CGPoint(x: direction.x * POINTS_PER_SEC, y: direction.y * POINTS_PER_SEC)

newPosition = CGPoint(x:currentPosition.x + velocity.x * CGFloat(dt), y:currentPosition.y + velocity.y * CGFloat(dt))
position = newPosition

Here’s what you’re doing with the code you just added:

  1. You calculate a vector that points in the direction the pig should travel and has a length representing the distance the pig should move in dt seconds.

    To calculate the vector, first you find the difference between the pig’s current location and the next waypoint and store it as offset, a CGPoint representing the differences in both the x and y directions.

    As you can see in the following image, the distance between the two points is the length of the hypotenuse of the right triangle formed between the pig’s current position and the waypoint.


    You divide offset‘s components by length to create a normalized vector (a vector of length 1) that points in the direction of the waypoint and you store it in direction.

    Finally, you multiply direction by POINTS_PER_SEC and store it in velocity, which now represents a vector pointing in the direction the pig should travel, with a length that is the distance the pig should travel in one second.

  2. You calculate the pig’s new position by multiplying velocity by dt and adding the result to the pig’s current position. Because velocity stores the distance the pig should travel in one second and dt holds the number of seconds that have passed since the last call to move, multiplying the two results in the distance the pig should travel in dt seconds.

You’re done here for the moment. It’s time to use your new class and move the pig.

Responding to Touches

Open GameScene.swift and find this line in init(size:):

let pig = SKSpriteNode(imageNamed: "pig_1")

Replace the above line with the following:

let pig = Pig(imageNamed: "pig_1")
pig.name = "pig"

You have simply replaced SKSpriteNode with your new subclass, Pig, and given it a name. You will use this name when you process new touches to identify pig nodes.

Add the following properties to GameScene, just below the class GameScene line:

var movingPig: Pig?
var lastUpdateTime: NSTimeInterval = 0.0
var dt: NSTimeInterval = 0.0

movingPig will hold a reference to the pig the user wants to move. There are times the user don’t want to move a pig so movingPig is an optional variable. lastUpdateTime will store the time of the last call to update and dt will store the time elapsed between the two most recent calls to update.

A few steps remain before you get to see your pig move. Find touchesBegan(_:,withEvent:) and replace its contents with the following:

let location = touches.anyObject()!.locationInNode(self)
let node = nodeAtPoint(location)
if node.name? == "pig" {
  let pig = node as Pig
  movingPig = pig

What happens here? First, you find the location of the touch within the scene. After that, you use nodeAtPoint() to identify the node at that location. The if statement uses the node’s name to see if the user touched a pig or something else, such as the background.

Note: You use the name property of SKNode to check for the pig. This is like UIView‘s tag property: a simple way to identify a node without needing to store a reference. Later, you’ll see another use case for the name property.

If the user touched a pig, you add location as a waypoint and set movingPig to the touched node. You’ll need this reference in the next method to add more points to the path.

To draw a path, after the first touch the user needs to move their finger while continuously touching the screen. Add the following implementation of touchesMoved(_:,withEvent:) to add more waypoints:

override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
  let location = touches.anyObject()!.locationInNode(scene)
  if let pig = movingPig {

This is a simple method. You get the next position of the user’s finger and if you found a pig in touchesBegan(_:,withEvent:), as indicated by a non-nil movingPig value, you add the position to this pig as the next waypoint.

Implement the final touch handling method to set movingPig to nil:

override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
  movingPig = nil

So far, you can store a path for the pig—now let’s make the pig follow this path. Add the following code to update() inside GameScene.swift:

dt = currentTime - lastUpdateTime
lastUpdateTime = currentTime
enumerateChildNodesWithName("pig", usingBlock: {node, stop in
  let pig = node as Pig

  1. First, you calculate the time since the last call to update and store it in dt. Then, you assign currentTime to lastUpdateTime so you have it for the next call.
  2. Here is the other use case for the name property. You use SKScene‘s method enumerateChildNodesWithName to enumerate over all nodes with the name pig. On these nodes, you call move, passing dt as the argument. Since SKNode has no method called move, you cast it to Pig to make Xcode and the compiler happy.

Now build and run, and let the pig follow your finger as you draw a path.

Pig following mouse

The pig doesn’t face in the direction it’s moving, but otherwise this is a good result!

But wait a minute—isn’t this a line drawing game? So where is the line?

Drawing Lines

Believe it or not, there is only one important step left to complete a line drawing game prototype that you can expand. Drawing the lines!

At the moment, only the pig knows the path it wants to travel, but the scene also needs to know this path to draw it. The solution to this problem is a new method for your Pig class.

Open Pig.swift and add the following method:

func createPathToMove() -> CGPathRef? {
  if wayPoints.count <= 1 {
    return nil
  var ref = CGPathCreateMutable()
  for var i = 0; i < wayPoints.count; ++i {
    let p = wayPoints[i]
    if i == 0 {
      CGPathMoveToPoint(ref, nil, p.x, p.y)
    } else {
      CGPathAddLineToPoint(ref, nil, p.x, p.y)
  return ref

Let's go over this step by step:

  1. First, you check if you have enough waypoints to create a path. A path has at least on line and a line needs at least 2 points. If you have less than 2 points you just return nil.
  2. Then, you create a mutable CGPathRef so you can add points to it.
  3. This for loop iterates over all the stored waypoints to build the path.
  4. Here you check if the path is just starting, indicated by an i value of zero. If so, you move to the point's location; otherwise, you add a line to the point. If this is confusing, think about how you would draw a path with pen and paper. CGPathMoveToPoint() is the moment you put the pen on the paper after moving it to the starting point, while CGPathAddLineToPoint() is the actual drawing with the pen on the paper.
  5. At the end, you return the path.

Open GameScene.swift and add this method to draw the pig's path:

func drawLines() {
  enumerateChildNodesWithName("line", usingBlock: {node, stop in
  enumerateChildNodesWithName("pig", usingBlock: {node, stop in
    let pig = node as Pig
    if let path = pig.createPathToMove() {          
      let shapeNode = SKShapeNode()
      shapeNode.path = path
      shapeNode.name = "line"
      shapeNode.strokeColor = UIColor.grayColor()
      shapeNode.lineWidth = 2
      shapeNode.zPosition = 1


Here’s what’s happening:

  1. You'll redraw the path every frame, so first you remove any old lines. To do so, you enumerate over all nodes with the name "line" and you remove them from the scene.
  2. Next, you enumerate over all the pigs in your scene.
  3. For each pig, you use the method you just added and try to get a new path. If you got a path you create an SKShapeNode and assign the path to it's path property. After that you name it "line". Next you set the stroke color of the shape to gray and the fill color to nil. You can use any color you want, but I think gray will be visible on the most backgrounds.
  4. Finally, you add shapeNode to your scene so that the scene will render it.

At last, to draw the path, add this line at the end of update() in GameScene.swift:


Build and run, ready your finger and watch as the game draws your path onscreen—and hopefully, your pig follows it!


Note: At the time of writing this tutorial, there appears to be a bug where shape nodes will not appear in the simulator. To see the line, simply run your code on an actual iOS device.

Continuous Movement

Actually, the pig stops moving after it reaches the last waypoint. For Hogville, it makes more sense and will be more challenging if the pigs keep moving. Otherwise, you could simply draw a path to the pigs straight to their pens and the game would be over!

Let's correct this. Open Pig.swift and add the following code at the end of move():

else {
  newPosition = CGPoint(x: currentPosition.x + velocity.x * CGFloat(dt),
                                   y: currentPosition.y + velocity.y * CGFloat(dt))
  position = newPosition

This code simply continues to move the pig with its most recently calculated velocity when it runs out of waypoints. Remember, velocity includes both direction and speed. The last step is to move the line
position = newPosition outside the if/else-statement and at the end of move.

Build and run, and watch your pig moving and moving and moving... until you can’t see it anymore. This little piggy went off screen!


What you need is a method to check if the pig is about to leave the screen and if so, switch its direction.

Also in Pig.swift, add this method:

func checkBoundaries(position: CGPoint) -> CGPoint {
  var newVelocity = velocity
  var newPosition = position
  let bottomLeft = CGPoint(x: 0, y: 0)
  let topRight = CGPoint(x:scene!.size.width, y:scene!.size.height)
  if newPosition.x <= bottomLeft.x {
    newPosition.x = bottomLeft.x
    newVelocity.x = -newVelocity.x
  } else if newPosition.x >= topRight.x {
    newPosition.x = topRight.x
    newVelocity.x = -newVelocity.x
  if newPosition.y <= bottomLeft.y {
    newPosition.y = bottomLeft.y
    newVelocity.y = -newVelocity.y
  } else if newPosition.y >= topRight.y {
    newPosition.y = topRight.y
    newVelocity.y = -newVelocity.y
  velocity = newVelocity
  return newPosition

This looks more complicated than it is, so let’s look more closely:

  1. First, you assign the current velocity and point to local variables.
  2. Here you define the important points on the screen. You can use bottomLeft to check if the pig is moving off screen from the left or bottom sides and topRight to check the top and right sides of the screen. You perform these checks inside the following if statements, one for each side of the screen.
  3. The first if statement checks the x value of newPosition. If this value is zero or less, the pig is leaving the screen from the left side. To avoid this, you set the pig's x-position to the left boundary—zero—and reverse the x-component of the velocity so the pig starts moving in the opposite direction. The other if statements do the same for the remaining three bounds of the screen.
  4. At the end, you change velocity to whatever value you calculated and then return newPosition.

To make use of this new method, change the last line in move() from this:

position = newPosition

To this:

position = checkBoundaries(newPosition)

Build and run your project again and watch the pig bounce off of the screen’s borders. You’ve got yourself a pig pen!


Rotating the Sprite

Before adding the actual gameplay, there are some minor improvements you should make. First, the pig doesn't rotate at all, which looks a bit weird. Like most animals, pigs normally walk facing forward rather than backward or to the side!

Rotate the pig so that it faces the direction it’s moving by adding the following line to the end of move() in Pig.swift:

zRotation = atan2(CGFloat(velocity.y), CGFloat(velocity.x)) + CGFloat(M_PI_2)

atan2 returns the angle between the x-axis and the given point. You add M_PI_2 because the pig image you use faces down and the value you receive from atan2 assumes the image faces to the right. Therefore, by adding M_PI_2, you rotate the pig by 90° counterclockwise.

A rotating pig

Note: If this is too much math for you, check out our open source SKTUtils library. The library includes a ton of helper functions that abstracts common math like this; for example it extends CGPoint so you can just access an angle property to convert a vector to an angle.

Animating the Sprite

Now that the pig faces the direction it's moving, it's time to add a pretty animation. Add the following new property at the top of Pig.swift:

var moveAnimation: SKAction 

Next, go to init(imageNamed:) and add the following lines directly before you call super.init():

let textures = [SKTexture(imageNamed:"pig_1"), SKTexture(imageNamed:"pig_2"), SKTexture(imageNamed:"pig_3")]
moveAnimation = SKAction.animateWithTextures(textures, timePerFrame:0.1)

This creates three textures, each of which represents a frame of the animation. The last line creates an SKAction that animates the pig’s movement using the three textures and stores the action in moveAnimation. The game will show each frame for 0.1 seconds, resulting in a nice walking animation for the pig. now do the same in init(coder:)

The last step is to run the animation. Go to move() and add the following lines at the beginning of the method:

if(actionForKey("moveAction") == nil) {
  runAction(moveAnimation, withKey:"moveAction")

First, you check if an animation named "moveAction" is already running and add it if there is no such animation. This ensures that the game adds the animation only once. Without this check, you wouldn't see the animation because it would start from the beginning every time you called move.

Build and run, and watch your pig animate around the screen as you direct it.


Your Gameplay Strategy

Let’s pause to think about how the gameplay will work and what you need to achieve it.

The basic idea behind Hogville is that pigs will appear at random positions on the left side of the screen and from there, move in random directions. The time between the appearances of each new pig will decrease until it reaches a threshold defined by you.

The player must herd all of these pigs to a trough of food where they can eat and after that, bring them to a barn where they can sleep. If any two pigs collide, the game is over.

Here are the needed steps:

  1. Add sprites for the food trough and barn.
  2. Spawn pigs over time.
  3. Add collision handling.
  4. Add logic that controls when a pig needs food or is ready to sleep.
  5. Handle win/lose conditions.

You will go step by step through this list to finish your game. Don't panic—this tutorial is here to guide you!

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 () {
  let bg = SKSpriteNode(imageNamed:"bg_2_grassy")
  bg.anchorPoint = CGPoint(x: 0, y: 0)
  let foodNode = SKSpriteNode(imageNamed:"trough_3_full")
  foodNode.name = "food"
  foodNode.position = CGPoint(x:250, y:200)
  foodNode.zPosition = 1

  // More code later

  homeNode = SKSpriteNode(imageNamed: "barn")
  homeNode.name = "home"
  homeNode.position = CGPoint(x: 500, y: 20)
  homeNode.zPosition = 1
  currentSpawnTime = 5.0

Let’s take a tour of this code:

  1. This is simply the code to create the background that you already have in init(size:). You'll remove it from that method soon.
  2. 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.
  3. 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():


Build and run, and you'll see the stage is now set:

Background added

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() {   
  currentSpawnTime -= 0.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"
  runAction(SKAction.sequence([SKAction.waitForDuration(currentSpawnTime), SKAction.runBlock({

Here’s a step-by-step breakdown of the code above:

  1. This decreases the time between spawns by 0.2 seconds every time the game spawns a pig.
  2. 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.
  3. 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’s zPosition to 1 makes sure the pig renders on top of the lines in the scene, which you’ve added with the default zPosition of zero.
  4. 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 calls spawnAnimal again after waitForDuration waits for currentSpawnTime seconds. Because you reduce currentSpawnTime each time you call this method, you end up calling spawnAnimal with less and less of a delay.

Now add a call to your method in init(size:), immediately after the call to loadLevel():


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.

Note: This tutorial assumes you have some experience using Sprite Kit’s built-in physics engine. If you don’t, work your way through this Sprite Kit Swift tutorial for beginners before proceeding. You’ll also find a good introduction in the book iOS Games by Tutorials, as well as more advanced chapters about the built-in physics engine.

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
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) {
  let firstNode = contact.bodyA.node
  let secondNode = contact.bodyB.node
  let collision = firstNode!.physicsBody!.categoryBitMask | secondNode!.physicsBody!.categoryBitMask
  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:

  1. 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.
  2. You perform a bitwise-OR of the categories of the two collided nodes and store it in collision.
  3. 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?

Adding Physics Bodies

To detect collisions, you have to configure a physics body on any sprite you want to engage in collisions. Start by configuring the trough in GameScene.swift by replacing the comment //More code later in loadLevel() with the following code:

foodNode.physicsBody = SKPhysicsBody(rectangleOfSize: foodNode.size)
foodNode.physicsBody!.categoryBitMask = ColliderType.Food.rawValue
foodNode.physicsBody!.dynamic = false

Your physics body needs geometry to define its shape. In this case, a rectangle with the node's size will suffice. Since this is the food trough, you assign the corresponding category to it. The last line tells the physics engine not to move this object. That is, things can bounce off of the object, but no force will affect the object itself.

Note: In this game, collisions will not affect the motion of any object—you will use the physics engine only for collision detection.

The second node that needs a physics body is the pig. Open Pig.swift and add the following to init(imagedNamed:), after the call to super.init.

physicsBody = SKPhysicsBody(circleOfRadius: size.width / 2.0)
physicsBody!.categoryBitMask = ColliderType.Animal.rawValue
physicsBody!.contactTestBitMask = ColliderType.Animal.rawValue | ColliderType.Food.rawValue
physicsBody!.collisionBitMask = 0

This is similar to what you did for the trough, except you use a circular physics shape because it fits the pig better than a rectangle. You set the category to indicate the object is an animal and you set contactTestBitMask to indicate that this node should produce contact notifications whenever it touches any other physics body that belongs to any category inside the bit mask.

You also set the object’s collisionBitMask to 0 to indicate to the physics engine that forces generated during collisions should not affect the pigs. Basically, you allow pigs to pass right through other physics objects.

Challenge: What kinds of nodes will generate contact notifications when a pig touches them?
[spoiler title="Solution"]According to contactTestBitMask, nodes that belong to either of the LDPhysicsCategoryAnimal or LDPhysicsCategoryFood categories will generate contact notifications. So you'll receive notifications whenever a pig touches another pig or the food trough.

Build and run, and let a pig collide with the trough and with another pig. Have a look at the console output and check that the correct logs appear.


Feeding the Pigs

With your collision detection working, you can make it possible to feed your pigs. Inside Pig.swift, add the following properties:

var hungry = true
var eating = false

These flags will keep track of the pig's current state.

Still inside Pig.swift, change move() by wrapping its current contents inside an if statement that checks to make sure the pig is not eating, as shown below:

if !eating {
  //existing move: code goes here.

This prevents your pig from moving while it's eating. As your mother always said, "Don't eat and run at the same time!"


When a pig is finished eating, you want it to start walking again. Add the following method to Pig.swift to send the pig off in a random direction:

func moveRandom() {
  let width = scene!.frame.width
  let height = scene!.frame.height
  let randomPoint = CGPoint(x:Int(arc4random_uniform(UInt32(width))),        
  wayPoints.append(CGPoint(x:randomPoint.x + 1, y:randomPoint.y + 1))

This method has three simple steps:

  1. First, you remove all existing waypoints to make the path truly random.
  2. Then, you get the width and height of the scene to have a range for the random numbers.
  3. With these values, you create a random CGPoint inside your scene and add it as a waypoint. This new waypoint is enough to get the pig moving again.

Now add the following method, which you'll call when the pig gets to the trough:

func eat() {
  if hungry {
    eating = true
    hungry = false
    let blockAction = SKAction.runBlock({
      self.eating = false
    runAction(SKAction.sequence([SKAction.waitForDuration(1.0), blockAction]))

Here’s how you feed a pig:

  1. When the pig is hungry, it will start eating.
  2. You remove the walking animation and set eating to true. Your pig will stand still on the trough and eat. Once it finishes eating it is no longer hungry, so you set hungry to false.
  3. Like everything in life, eating takes time, so you run a sequence action that waits for a second and then executes blockAction, which sets eating to false and calls the method you just added to start the pig walking again. You could decrease the eating time to make the game easier.

Now open GameScene.swift and find didBeginContact. Replace the NSLog statement that logs "Food collision detected" with the following code:

var pig: Pig!

if firstNode!.name == "pig" {
  pig = firstNode as Pig
} else {
  pig = secondNode as Pig

You know this collision is between the pig and the trough, so you simply figure out which node is the pig and call eat on it.

Build and run, and guide the pig to a trough. He will stop to eat, then move off to a random direction.


Can't you just imagine that pig squealing with delight? ;]

Finishing the Game

Your game is almost complete, but you still need win and lose conditions. This is an "endless game" in that the player keeps going as long as they can until they lose.

However, you need to make it so that pigs who have eaten can be guided to the barn, at which point they will be removed from the scene. A good place for this check is the move() method of Pig.

Open Pig.swift and add the following property:

var removing = false

You'll use this flag to mark pigs while they are in the process of leaving the game. This is because you will make them fade off the screen when they're leaving the game, and you want to have a flag that prevents you from running this animation twice.

Still in Pig.swift, add the following new method:

func checkForHome() {
  if hungry || removing {
  let s = scene as GameScene       
  let homeNode = s.homeNode
  if frame.intersects(homeNode.frame) {
    removing = true
    wayPoints.removeAll(keepCapacity: false)
                SKAction.group([SKAction.fadeAlphaTo(0.0, duration: 0.5),     
                SKAction.moveTo(homeNode.position, duration: 0.5)]), 

What’s happening here?

  1. Hungry pigs won’t go to sleep, so you first check if the pig is hungry or is already set to be removed from the game.
  2. Here you get the homeNode.
  3. You then check if the pig's frame overlaps the barn's. If that's the case, you set the pig's removing flag to true and clear its waypoints and any running actions.
  4. Here you run another sequence of actions that first runs a group of actions simultaneously, and when those are done it removes the pig from the scene. The group of actions fades out the pig's sprite while it moves the pig to the center of the barn.

Now call this method by adding the following line to move(), right after the line that sets the pig's zRotation:


Build and run, and guide those pigs home!


Game Over: When Pigs Collide

When two pigs collide, the game will show a “Game Over!” message and then give the player the chance to start a new round.

Open GameScene.swift and add a BOOL variable to indicate when the game is over:

var gameOver = false

You don't want the pigs moving or spawning when the game is over, so wrap the current contents of update() inside an if statement, as follows:

if !gameOver {
  // existing code here

Also, add the following code at the beginning of spawnAnimal to make sure new pigs don’t appear after the player has lost:

if gameOver {

Now add this method, which you'll call when two pigs collide:

func handleAnimalCollision() {
  gameOver = true
  let gameOverLabel = SKLabelNode(fontNamed: "Thonburi-Bold")
  gameOverLabel.text = "Game Over!"
  gameOverLabel.name = "label"
  gameOverLabel.fontSize = 35.0
  gameOverLabel.position = CGPointMake(size.width / 2.0, size.height / 2.0 + 20.0)
  gameOverLabel.zPosition = 5
  let tapLabel = SKLabelNode(fontNamed: "Thonburi-Bold")
  tapLabel.text = "Tap to restart."
  tapLabel.name = "label"
  tapLabel.fontSize = 25.0
  tapLabel.position = CGPointMake(size.width / 2.0, size.height / 2.0 - 20.0)
  tapLabel.zPosition = 5

Here you first set gameOver to true, which ends the game. You add two labels centered on the screen telling the user that the game is over and that they can tap the screen to restart. You set each label's zPosition to five to ensure they are in front of all other nodes.

Now call this new method in didBeganContact(). Replace the NSLog statement that logs “Animal collision detected” with the following line:


You told the player that they can tap the screen to restart, but right now that isn't true. Add the following method to GameScene.swift to handle restarting:

func restartGame() {
  enumerateChildNodesWithName("line", usingBlock: {node, stop in
  enumerateChildNodesWithName("pig", usingBlock: {node, stop in
  enumerateChildNodesWithName("label", usingBlock: {node, stop in
  currentSpawnTime = 5.0
  gameOver = false

restartGame() removes all lines, pigs and labels from the scene, sets the spawn time back to five seconds, sets gameOver to false and begins spawning animals.

You need to call this method if the user taps the screen while the game is over, so add the following lines to the top of touchesBegan(_:,withEvent:):

if gameOver {

All right, then! Build and run once more, and enjoy the line drawing game you created. See if you’re a natural-born swineherd.


Adding Polish

You may have noticed some places where the game behaves strangely. For example, look at the behavior of spawning pigs—they appear to walk in place! You can fix this easily by calling moveRandom on the pigs when you spawn them.

Inside GameScene.swift, call this method in spawnAnimal, just after the line that adds the pig to the scene:


Build and run, and now the pigs move right away:


Now let’s take a closer look at the way your game currently handles touches. Run the app and add a path to a pig, but before the pig arrives at the endpoint, try to change the path by drawing a new one.

As you can see, the pig initially ignores your second command and continues along the first path. Then, after reaching the end point, it moves back to the place where you tried to change direction and follows the second path. Every new path is just tacked onto the old one!

It would be much better if the game replaced the old path with a new path. To make this happen, first open Pig.swift and add a new method:

func clearWayPoints() {
  wayPoints.removeAll(keepCapacity: false)

This method simply removes all objects from wayPoints.

Now go to GameScene.swift and add the following line to touchesBegan(_:,withEvent:), just above the line that calls addMovingPoint():


You call clearWayPoints() every time the player touches a pig.

Build and run, and now the pigs listen to your every command!


Challenge: Add a HUD to the scene showing the player how many pigs they have removed. See if you can accomplish this by yourself before checking the tips below.
[spoiler title="Tips"]

  • Add an variable named animalCount inside GameScene. You'll want to increment it every time you remove a pig, but because the logic to remove the pig is in Pig.swift, you should add a new method to GameScene, perhaps named pigRemoved, and call it from checkForHome in Pig.swift.
  • Add an SKLabelNode to your scene. Be sure to set its zPosition to a value above the other nodes. Take a look at the other labels you added for hints. For easy access to the label, you should hold a reference to it—maybe in an variable named hud?
  • Update the text of your hud whenever you update animalCount.
  • Don't forget to reset the counter when you restart the game.


Where to Go From Here?

Here is the final project with all the code from the above tutorial.

There is still a lot of room for improvement in your game. But that’s OK—it gives you plenty of opportunities to practice your skills, old and new!

  • You could reduce the area searched when removing waypoints, which would make the pig look like it was on top of the line rather than having the line always just out of the pig's reach.
  • If you move your finger slowly while creating a line, it currently adds far too many points, producing an unpleasant visual experience. You could apply a smoothing algorithm to the points, such as the basic one described here, to reduce redundant points and improve the quality of both the line rendering and the pig's motion.
  • It's currently possible for a new pig to spawn with no warning right on top of an existing pig, ending the game immediately. How would you fix this problem?
  • You could add an indicator to warn the player of incoming pigs.
  • You could add different levels that you load from a plist, as well as different animals.
  • You could use the additional trough images provided with the starter project to make the trough appear empty after a certain amount of feeding, and require the player to refill the trough by tapping on it.
  • To tell you the truth, I really don't know why the game should end when two animals collide. It would be funnier if the animals began running in random directions for a short time after a collision. Because everything must come to an end (this tutorial, too) you could add a counter that ends the game after five collisions.

Whether or not you implement any of these suggestions, I'm sure you have your own excellent ideas, and now you know more of what you need to bring them to the App Store. Make sure to drop me a note when your game is available.

If you want to learn more about creating games like this, check out our book iOS Games by Tutorials. We’ll teach you everything you need to know – from physics, to tile maps, to particle systems, and even how to make your games “juicy” with polish and special effects.

If you have any questions or comments about this tutorial, please join the discussion below!




More like this