How to Make a Line Drawing Game with Sprite Kit

Learn how to make a line drawing game like Flight Control and Harbor Master in this Sprite Kit tutorial! 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 MyScene.m by replacing the comment //More code later in loadLevel with the following code:

foodNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:foodNode.size];
foodNode.physicsBody.categoryBitMask = LDPhysicsCategoryFood;
foodNode.physicsBody.dynamic = NO;

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.

The second node that needs a physics body is the pig. Open Pig.m and import the scene in order to access the category enum:

#import "MyScene.h"

Then add the following to initWithImageNamed:, after the line that initializes _moveAnimation:

self.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:self.size.width / 2.0f];
self.physicsBody.categoryBitMask = LDPhysicsCategoryAnimal;
self.physicsBody.contactTestBitMask = LDPhysicsCategoryAnimal | LDPhysicsCategoryFood;
self.physicsBody.collisionBitMask = kNilOptions;

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 kNilOptions (which is simply zero) 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.m, add the following instance variables to the @implementation section:

BOOL _hungry;
BOOL _eating;

These flags will keep track of the pig's current state.

To begin with a hungry pig, add the following line inside initWithImageNamed:, just after the lines that set up the physics body:

_hungry = YES;

Still inside Pig.m, 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.m to send the pig off in a random direction:

- (void)moveRandom {
  //1
  [_wayPoints removeAllObjects];
    
  //2
  int width = (int)CGRectGetWidth(self.scene.frame);
  int height = (int)CGRectGetHeight(self.scene.frame);
  //3
  CGPoint randomPoint = CGPointMake(arc4random() % width,  arc4random() % height);
  [_wayPoints addObject:[NSValue valueWithCGPoint:randomPoint]];
}

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:

- (void)eat {
  //1
  if(_hungry) {
    //2
    [self removeActionForKey:@"moveAction"];
    _eating = YES;
    _hungry = NO;
        
    //3
    SKAction *blockAction = [SKAction runBlock:^{
      _eating = NO;
      [self moveRandom];
    }];
        
    [self 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 YES. Your pig will stand still on the trough and eat. Once it finishes eating it is no longer hungry, so you set _hungry to NO.
  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 NO and calls the method you just added to start the pig walking again. You could decrease the eating time to make the game easier.

You want to call eat from your scene, so add the declaration to the interface in Pig.h:

- (void)eat;

Now open MyScene.m and find didBeginContact:. Replace the NSLog statement that logs "Food collision detected" with the following code:

if([firstNode.name isEqualToString:@"pig"]) {
  [(Pig *)firstNode eat];
} else {
  [(Pig *)secondNode 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.m and add the following instance variable to the @implementation:

BOOL _removing;

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.m, add the following new method:

- (void)checkForHome {
  //1
  if(_hungry || _removing) {
    return;
  }
    
  //2
  SKSpriteNode *homeNode = ((MyScene *)self.scene).homeNode;
  CGRect homeRect = homeNode.frame;
    
  //3
  if(CGRectIntersectsRect(self.frame, homeRect)) {
    _removing = YES;
    [_wayPoints removeAllObjects];
    [self removeAllActions];
    //4
    [self runAction:
     [SKAction sequence:
      @[[SKAction group:
         @[[SKAction fadeAlphaTo:0.0f 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 frame rectangle of 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 YES 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:

[self checkForHome];

Build and run, and guide those pigs home!

GoHome