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.

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

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
  pig.addMovingPoint(location)
  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 {
    pig.addMovingPoint(location)
  }
}

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
  pig.move(self.dt)
})

  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? {
  //1
  if wayPoints.count <= 1 {
    return nil
  }
  //2
  var ref = CGPathCreateMutable()
    
  //3
  for var i = 0; i < wayPoints.count; ++i {
    let p = wayPoints[i]
            
    //4
    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() {
  //1
  enumerateChildNodesWithName("line", usingBlock: {node, stop in
    node.removeFromParent()
  })
     
  //2
  enumerateChildNodesWithName("pig", usingBlock: {node, stop in
    //3
    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

      self.addChild(shapeNode)
    }
  })
}

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:

drawLines()

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

Drawn_Lines

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.