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 4 of 6 of this article. Click here to view the first page.

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:

  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():

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() {   
  //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:

  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():

spawnAnimal()

Build and run, and watch the pigs appear faster and faster over time.

Spawning_Pigs

Detecting Collisions

As you can see in the image above, the pigs move through the trough, barn and even other pigs.

pigs_gone_wild

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
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:

  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?