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.
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
50 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 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.
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.
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!"
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:
- 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:
- (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:
- When the pig is hungry, it will start eating.
- You remove the walking animation and set
_eating
toYES
. Your pig will stand still on the trough and eat. Once it finishes eating it is no longer hungry, so you set_hungry
toNO
. - 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
toNO
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.
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?
- 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
frame
rectangle of thehomeNode
. - You then check if the pig's frame overlaps the barn's. If that's the case, you set the pig's
_removing
flag toYES
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
:
[self checkForHome];
Build and run, and guide those pigs home!