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?
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:
- This is simply the code to create the background that you already have in
initWithSize:
. You'll remove it from that method soon. - 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. - 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:
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:
- This decreases the time between spawns by 0.2 seconds every time the game spawns a pig.
- 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.
- 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’szPosition
to 1 makes sure the pig renders on top of the lines in the scene, which you’ve added with the defaultzPosition
of zero. - 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 callsspawnAnimal
again afterwaitForDuration
waits for_currentSpawnTime
seconds. Because you reduce_currentSpawnTime
each time you call this method, you end up callingspawnAnimal
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.
Detecting Collisions
As you can see in the image above, the pigs move through the trough, barn and even other pigs.
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.
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:
- 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.
- You perform a bitwise-OR of the categories of the two collided nodes and store it in
collision
. - 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?