How To Create A Breakout Game Using SpriteKit

Learn how to create a breakout game for iOS using SpriteKit! By Barbara Reichart.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

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.

  1. 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.
  2. 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.

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:

Game Over

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.

  1. Some useful variables like the number of blocks you want and their width.
  2. 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.
  3. 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!

Breakout blocks at the beginning of the game. Nice and orderly.

Breakout blocks at the beginning of the game. Nice and orderly.

Not quite as expected…

This did not quite go as expected...

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 :]

Winning the Game

To let the player actually win the game, add this method to MyScene.m.

-(BOOL)isGameWon {
    int numberOfBricks = 0;
    for (SKNode* node in self.children) {
        if ([node.name isEqual: blockCategoryName]) {
            numberOfBricks++;
        }
    }
    return numberOfBricks <= 0;
}

The new method checks to see how many bricks are left in the scene by going through all the scene’s children. For each child, it checks whether the child name is equal to blockCategoryName. If there are no bricks left, the player has won the game and the method returns YES.

Now, go back to didBeginContact: and replace the TODO line in the final if condition with the following:

    if ([self isGameWon]) {
        GameOverScene* gameWonScene = [[GameOverScene alloc] initWithSize:self.frame.size playerWon:YES];
        [self.view presentScene:gameWonScene];
    }

The code checks whether the player has won by calling the new method you just implemented. If he has won, you show the GameOverScene with a message indicating victory.

Barbara Reichart

Contributors

Barbara Reichart

Author

Over 300 content creators. Join our team.