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

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.

Collision_Log

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!"

eat_run_mom

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:

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

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

HappyPig

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?

  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:

checkForHome()

Build and run, and guide those pigs home!

GoHome