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

Completing the Scene

To complete the first step, open MyScene.h and add the following property to the interface:

@property (nonatomic, strong) SKSpriteNode *homeNode;

homeNode will display an image of a barn and act as the final goal for your pigs. You will need to access this property later in the Pig class, so you make it a public property instead of a private variable.

Then open MyScene.m and add the following instance variable to the @implementation section:

NSTimeInterval _currentSpawnTime;

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.

Add this new method in MyScene.m to set up the sprites in your scene:

- (void)loadLevel {
  //1
  SKSpriteNode *bg = [SKSpriteNode spriteNodeWithImageNamed:@"bg_2_grassy"];
  bg.anchorPoint = CGPointZero;
  [self addChild:bg];
    
  //2
  SKSpriteNode *foodNode = [SKSpriteNode spriteNodeWithImageNamed:@"trough_3_full"];
  foodNode.name = @"food";
  foodNode.zPosition = 0;
  foodNode.position = CGPointMake(250.0f, 200.0f);
  // More code later

  [self addChild:foodNode];
    
  //3
  self.homeNode = [SKSpriteNode spriteNodeWithImageNamed:@"barn"];
  self.homeNode.name = @"home";
  self.homeNode.zPosition = 0;
  self.homeNode.position = CGPointMake(380.0f, 20.0f);
  [self addChild:self.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 initWithSize:. 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 within the if statement of initWithSize: with the following call to loadLevel:

[self 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 MyScene.m:

- (void)spawnAnimal {   
  //1
  _currentSpawnTime -= 0.2;
    
  //2
  if(_currentSpawnTime < 1.0) {
    _currentSpawnTime = 1.0;
  }
    
  //3
  Pig *pig = [[Pig alloc] initWithImageNamed:@"pig_1"];
  pig.position = CGPointMake(20.0f, arc4random() % 300);
  pig.name = @"pig";
  pig.zPosition = 1;

  [self addChild:pig];

  //4
  [self runAction:
   [SKAction sequence:@[[SKAction waitForDuration:_currentSpawnTime], 
                        [SKAction performSelector:@selector(spawnAnimal) onTarget:self]]]];
}

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 initWithSize:. 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 MyScene.m in initWithSize:, immediately after the call to [self loadLevel];:

[self 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 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 scene. Open MyScene.h and add the following typedef above the @interface part:

typedef NS_OPTIONS(uint32_t, LDPhysicsCategory) {
  LDPhysicsCategoryAnimal = 1 << 0,
  LDPhysicsCategoryFood = 1 << 1,
};

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 open MyScene.m and add a category extension to your scene.

@interface MyScene () <SKPhysicsContactDelegate>

@end

This tells the compiler that your scene is implementing the SKPhysicsContactDelegate protocol.

Now find initWithSize: and add this right before the call to loadLevel:

self.physicsWorld.gravity = CGVectorMake(0.0f, 0.0f);
self.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 MyScene.m:

- (void)didBeginContact:(SKPhysicsContact *)contact {
  //1
  SKNode *firstNode = contact.bodyA.node;
  SKNode *secondNode = contact.bodyB.node;
    
  //2
  uint32_t collision = firstNode.physicsBody.categoryBitMask | secondNode.physicsBody.categoryBitMask;
    
  //3
  if(collision == (LDPhysicsCategoryAnimal | LDPhysicsCategoryAnimal)) {
    NSLog(@"Animal collision detected");
  } else if(collision == (LDPhysicsCategoryAnimal | LDPhysicsCategoryFood)) {
    NSLog(@"Food collision detected.");
  } else {
    NSLog(@"Error: Unknown collision category %d", 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?