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?
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.
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.
[/spoiler]
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() {
//1
wayPoints.removeAll(keepCapacity:false)
//2
let width = scene!.frame.width
let height = scene!.frame.height
//3
let randomPoint = CGPoint(x:Int(arc4random_uniform(UInt32(width))),
y:Int(arc4random_uniform(UInt32(height))))
wayPoints.append(randomPoint)
wayPoints.append(CGPoint(x:randomPoint.x + 1, y:randomPoint.y + 1))
}
This method has three simple steps:
- First, you remove all existing waypoints to make the path truly random.
- Then, you get the width and height of the scene to have a range for the random numbers.
- 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() {
//1
if hungry {
//2
removeActionForKey("moveAction")
eating = true
hungry = false
//3
let blockAction = SKAction.runBlock({
self.eating = false
self.moveRandom()
})
runAction(SKAction.sequence([SKAction.waitForDuration(1.0), blockAction]))
}
}
Here’s how you feed a pig:
- When the pig is hungry, it will start eating.
- You remove the walking animation and set
eating
totrue
. Your pig will stand still on the trough and eat. Once it finishes eating it is no longer hungry, so you sethungry
tofalse
. - Like everything in life, eating takes time, so you run a sequence action that waits for a second and then executes
blockAction
, which setseating
tofalse
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
pig.eat()
} else {
pig = secondNode as Pig
pig.eat()
}
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() {
//1
if hungry || removing {
return
}
//2
let s = scene as GameScene
let homeNode = s.homeNode
//3
if frame.intersects(homeNode.frame) {
removing = true
wayPoints.removeAll(keepCapacity: false)
removeAllActions()
//4
runAction(SKAction.sequence([
SKAction.group([SKAction.fadeAlphaTo(0.0, duration: 0.5),
SKAction.moveTo(homeNode.position, duration: 0.5)]),
SKAction.removeFromParent()]))
}
}
What’s happening here?
- 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.
- Here you get the
homeNode
. - You then check if the pig's frame overlaps the barn's. If that's the case, you set the pig's
removing
flag totrue
and clear its waypoints and any running actions. - 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
:
checkForHome()
Build and run, and guide those pigs home!