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?
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.
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)
})
- First, you calculate the time since the last call to
update
and store it indt
. Then, you assigncurrentTime
tolastUpdateTime
so you have it for the next call. - Here is the other use case for the
name
property. You useSKScene
‘s methodenumerateChildNodesWithName
to enumerate over all nodes with the namepig
. On these nodes, you callmove
, passingdt
as the argument. SinceSKNode
has no method calledmove
, you cast it toPig
to make Xcode and the compiler happy.
Now build and run, and let the pig follow your finger as you draw a path.
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:
- 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.
- Then, you create a mutable
CGPathRef
so you can add points to it. - This
for
loop iterates over all the stored waypoints to build the path. - 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, whileCGPathAddLineToPoint()
is the actual drawing with the pen on the paper. - 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:
- 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. - Next, you enumerate over all the pigs in your scene.
- 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. - 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!