How To Create A Breakout Game Using SpriteKit
Learn how to create a breakout game for iOS using SpriteKit! By Barbara Reichart.
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 Create A Breakout Game Using SpriteKit
40 mins
Adding a Game Over Scene
Unfortunately, your player cannot see log messages when they lose the game. Instead, when they lose, you want to show him/her some sort of visual indication on screen. You must create a little game over scene for that purpose.
Go to File\New\File…, choose the iOS\Cocoa Touch\Objective-C class template, and click Next. Name the class GameOverScene, make it a subclass of SKScene, click Next, and then Create.
Switch to GameOverScene.h and add the following method prototype before the @end
line:
-(id)initWithSize:(CGSize)size playerWon:(BOOL)isWon;
This new initializer adds a parameter indicating whether the player has won or lost. This allows you to implement only one scene for both victory and defeat. Very convenient :)
Now, replace the code in GameOverScene.m with the following:
#import "GameOverScene.h"
#import "MyScene.h"
@implementation GameOverScene
-(id)initWithSize:(CGSize)size playerWon:(BOOL)isWon {
self = [super initWithSize:size];
if (self) {
SKSpriteNode* background = [SKSpriteNode spriteNodeWithImageNamed:@"bg.png"];
background.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
[self addChild:background];
// 1
SKLabelNode* gameOverLabel = [SKLabelNode labelNodeWithFontNamed:@"Arial"];
gameOverLabel.fontSize = 42;
gameOverLabel.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
if (isWon) {
gameOverLabel.text = @"Game Won";
} else {
gameOverLabel.text = @"Game Over";
}
[self addChild:gameOverLabel];
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
MyScene* breakoutGameScene = [[MyScene alloc] initWithSize:self.size];
// 2
[self.view presentScene:breakoutGameScene];
}
@end
This code is pretty standard. So, I will only explain the commented parts that might be new to you.
Note: You can find out which fonts are installed on a device using this helpful snippet.
- Creates a label to display the victory or defeat message.
SKLabelNode
is a very useful class used to display text using any font installed on the device.Note: You can find out which fonts are installed on a device using this helpful snippet.
- When a touch is detected on the game over scene, you display the game scene again so that the user is taken back to another session of the game.
Note: You can find out which fonts are installed on a device using this helpful snippet.
The new scene is done. Time to add it to the game. Add the following import to the top of MyScene.m:
#import "GameOverScene.h"
Then, replace the NSLog
statement line in didBeginContact:
with the following code:
GameOverScene* gameOverScene = [[GameOverScene alloc] initWithSize:self.frame.size playerWon:NO];
[self.view presentScene:gameOverScene];
Now, build and run the game and play it till your paddle misses the ball:
Alright, now you’re getting somewhere! But what fun is a game where you can’t win?
Adding Some Blocks – and They Are Gone…
Add the following code to initWithSize:
in MyScene.m to introduce the blocks to the scene:
// 1 Store some useful variables
int numberOfBlocks = 3;
int blockWidth = [SKSpriteNode spriteNodeWithImageNamed:@"block.png"].size.width;
float padding = 20.0f;
// 2 Calculate the xOffset
float xOffset = (self.frame.size.width - (blockWidth * numberOfBlocks + padding * (numberOfBlocks-1))) / 2;
// 3 Create the blocks and add them to the scene
for (int i = 1; i <= numberOfBlocks; i++) {
SKSpriteNode* block = [SKSpriteNode spriteNodeWithImageNamed:@"block.png"];
block.position = CGPointMake((i-0.5f)*block.frame.size.width + (i-1)*padding + xOffset, self.frame.size.height * 0.8f);
block.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:block.frame.size];
block.physicsBody.allowsRotation = NO;
block.physicsBody.friction = 0.0f;
block.name = blockCategoryName;
block.physicsBody.categoryBitMask = blockCategory;
[self addChild:block];
}
This code creates three blocks with some padding so that they are centered on the screen.
- Some useful variables like the number of blocks you want and their width.
- Here you calculate the x offset. This is the distance between the left border of the screen and the first block. You calculate it by subtracting the width of all three blocks and their padding from the screen width and then dividing it by two.
- Create three blocks, configure each with the proper physics properties, and position each one using blockWidth, padding, and xOffset.
The blocks are now in place. Build and run your game and check it out!
Hmm … not quite what you wanted, right? The blocks move around instead of being destroyed when touched by the ball.
In order to listen to collisions between the ball and blocks, you must update the contactTestBitMask
of the ball like this (note that this is an existing line of code in initWithSize:
– you simply need to add an extra category to it.):
ball.physicsBody.contactTestBitMask = bottomCategory | blockCategory;
The above executes a bitwise OR operation on bottomCategory
and blockCategory
. The result is that the bits for those two particular categories are set to one while all other bits are still zero. Now, collisions between ball and floor as well as ball and blocks will be sent to to the delegate.
The only thing left to do is to handle the delegate notifications accordingly. Add the following to the end of didBeginContact:
:
if (firstBody.categoryBitMask == ballCategory && secondBody.categoryBitMask == blockCategory) {
[secondBody.node removeFromParent];
//TODO: check if the game has been won
}
The above lines check whether the collision is between the ball and a block. If this is the case, you remove the block involved in the collision.
Hopefully, you have a good understanding of bit masks in general and the contactTestBitMask
in particular. As you read earlier, bodies also have a second bit mask – the collisionBitMask
. It determines whether two bodies interact with each other. This allows you to control whether the ball bounces off the bricks or flies right through them, for instance.
If you want to practice working with bit masks, set the collisionBitMask
of the ball so that it goes straight through blocks while destroying them.
[spoiler title=”Using collisionBitMask”]
ball.physicsBody.collisionBitMask = paddleCategory;
Setting the collisionBitMask
to paddleCategory
means that the ball now only physically interacts with the paddle and not the blocks. For the barriers around the screen everything stays the same as they do not have any category assigned.
block.physicsBody.collisionBitMask = 0;
The above line sets the collisionBitMask
for the block to zero. This sets the block to not react to other bodies hitting it.
[/spoiler]
Have you overcome the challenge and tasted the thrill of victory? Time to give your player the same satisfaction of tasting victory :]