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

Adding the Paddle

It wouldn’t be a Breakout game without a paddle, now would it?

Construct the paddle (and its companion physics body) in initWithSize: in MyScene.m by adding this code just after you create and configure the ball:

     SKSpriteNode* paddle = [[SKSpriteNode alloc] initWithImageNamed: @"paddle.png"];
     paddle.name = paddleCategoryName;
     paddle.position = CGPointMake(CGRectGetMidX(self.frame), paddle.frame.size.height * 0.6f);
     [self addChild:paddle];
     paddle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:paddle.frame.size];
     paddle.physicsBody.restitution = 0.1f;
     paddle.physicsBody.friction = 0.4f;
     // make physicsBody static
     paddle.physicsBody.dynamic = NO;

Most of the code is similar to the code used when creating the ball. However, this time you use a rectangle to form the physics body as it more closely matches what is visible on screen.

Here you need to make sure that the paddle is static. This ensures that the paddle does not react to forces and impulses. You will soon see why this is important.

If you build and run, you’ll see your paddle in the scene, and the ball will bounce off it:

The most boring game ever :/

The most boring game ever :/

However, this isn’t much fun, because you can’t move the paddle yet!

Moving the Paddle

So let’s get moving! Moving the paddle is going to require detecting touches. You can detect touches in MyScene by implementing the following touch handling methods:

-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event;
-(void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event;
-(void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event;

But, before you implement the above methods, you need to add a property. Add the following code just above the @implementation line in MyScene.m:

@interface MyScene()

@property (nonatomic) BOOL isFingerOnPaddle;

@end

This defines a property that stores whether the player tapped the paddle or not. You will need it to implement the dragging of the paddle.

Now, go ahead and add the code to implement touchesBegan:withEvent: to MyScene.m as follows:

-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
    /* Called when a touch begins */
    UITouch* touch = [touches anyObject];
    CGPoint touchLocation = [touch locationInNode:self];
    
    SKPhysicsBody* body = [self.physicsWorld bodyAtPoint:touchLocation];
    if (body && [body.node.name isEqualToString: paddleCategoryName]) {
        NSLog(@"Began touch on paddle");
        self.isFingerOnPaddle = YES;
    }
}

The above code gets the touch and uses it to find the location on the scene where the touch occurred. Next, it uses bodyAtPoint: from physicsWorld to find the body (if any) at that location.

Next, it checks whether there was a body at the tap location and if yes, whether that body is the paddle. This is where the object names you set up earlier come in to play – you can check for a specific object by checking its name. If the object at the tap location is a paddle, then a log message is sent to the console and isFingerOnPaddle is set to YES.

Now you can build and run the project again. When you tap the paddle, you should see a log message in the console.

You have touches :)

You successfully receive touches :)

Now, go ahead and add the code for touchesMoved:withEvent::

-(void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
    // 1 Check whether user tapped paddle
    if (self.isFingerOnPaddle) {
        // 2 Get touch location
        UITouch* touch = [touches anyObject];
        CGPoint touchLocation = [touch locationInNode:self];
        CGPoint previousLocation = [touch previousLocationInNode:self];
        // 3 Get node for paddle
        SKSpriteNode* paddle = (SKSpriteNode*)[self childNodeWithName: paddleCategoryName];
        // 4 Calculate new position along x for paddle
        int paddleX = paddle.position.x + (touchLocation.x - previousLocation.x);
        // 5 Limit x so that the paddle will not leave the screen to left or right
        paddleX = MAX(paddleX, paddle.size.width/2);
        paddleX = MIN(paddleX, self.size.width - paddle.size.width/2);
        // 6 Update position of paddle
        paddle.position = CGPointMake(paddleX, paddle.position.y);
    }
}

This is where most of the paddle movement logic comes in.

  1. Check whether the player is touching the paddle.
  2. If yes, then you need to update the position of the paddle depending on how the player moves their finger. To do this, you get the touch location and previous touch location.
  3. Get the SKSpriteNode for the paddle.
  4. Take the current position and add the difference between the new and the previous touch locations.
  5. Before repositioning the paddle, limit the position so that the paddle will not go off the screen to the left or right.
  6. Set the position of the paddle to the position you just calculated.

Don’t believe me? Make the paddle dynamic and set a low gravity, e.g. this

Looks quite messed up, doesn’t it?

Note: You might have noticed that the code directly manipulates the position of the paddle. You can do this because you made the paddle static. You should never change the position of a dynamic body directly, as it can break the physics simulation and lead to really weird behavior.
       self.physicsWorld.gravity = CGPointMake(-0.1, -0.1);
       self.physicsWorld.gravity = CGPointMake(-0.1, -0.1);

The only thing left in touch handling is to do some cleanup in touchesEnded:withEvent: as follows:

-(void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
    self.isFingerOnPaddle = NO;
}

Here, you set the isFingerOnPaddle property to NO. This ensures that when the player takes their finger off the screen and then taps it again, the paddle does not jump around to the previous touch location.

Perfect! When you build and run the project now, the ball will bounce around the screen and you can influence its movement using the paddle.

SpriteKit Makes First Contact!

No! Not that type of first contact. (image by DeviantArt user hugoo13, Creative Commons Licensed)

No! Not that type of first contact.(image by DeviantArt user hugoo13, Creative Commons Licensed)

So far, you have a ball that bounces around the screen and a paddle you can move around via touch. While this is really fun to toy around with, to make it a game you need a way for your player to win and lose the game. Losing should happen when the ball touches the bottom of the screen instead of hitting the paddle. But how do you detect this scenario using SpriteKit?

SpriteKit can detect the contact between two physics bodies. However, for this to work properly, you need to follow a few steps to set things up a certain way. I’ll give you a short overview here and explain each of the steps in more detail later. So, here you go:

  • Set up physics body bit masks: In your game you might have several different types of physics bodies – for example, you can have the player, enemies, bullets, bonus items etc. To uniquely identify these different types of physics bodies, each physics body can be configured using several bit masks. These include:
    • categoryBitMask: This bit mask identifies the category a body belongs to. You use categories to to define a body’s interaction with other bodies. The categoryBitMask is a 32-bit integer, where each bit represents one category. So you can have up to 32 custom categories in your game. This should be enough for most games to set up a separate category for each object type. For more complex games it can be useful to remember that each body can be in several categories. So through smart design of the categories you could even overcome the limitation of 32 categories.
    • contactTestBitMask: Setting a bit in this bitmask causes SpriteKit to notify the contact delegate when the body touches another body assigned to that particular category. By default, all bits are cleared – you are not notified about any contacts between objects. For best performance you should only set bits in the contacts mask for interactions you are really interested in.
    • collisionBitMask: Here, you can define which bodies can collide with this physics body. You can use this, for example, to avoid collision calculations for a very heavy body when it collides with a much lighter body as this would only make negligible changes to the heavy body’s velocity. But you can also use it to allow two bodies to pass right through each other.
  • Set and implement the contact delegate: The contact delegate is a property of SKPhysicsWorld. It will be notified when two bodies with the proper contactTestBitMasks begin and end colliding.
  • categoryBitMask: This bit mask identifies the category a body belongs to. You use categories to to define a body’s interaction with other bodies. The categoryBitMask is a 32-bit integer, where each bit represents one category. So you can have up to 32 custom categories in your game. This should be enough for most games to set up a separate category for each object type. For more complex games it can be useful to remember that each body can be in several categories. So through smart design of the categories you could even overcome the limitation of 32 categories.
  • contactTestBitMask: Setting a bit in this bitmask causes SpriteKit to notify the contact delegate when the body touches another body assigned to that particular category. By default, all bits are cleared – you are not notified about any contacts between objects. For best performance you should only set bits in the contacts mask for interactions you are really interested in.
  • collisionBitMask: Here, you can define which bodies can collide with this physics body. You can use this, for example, to avoid collision calculations for a very heavy body when it collides with a much lighter body as this would only make negligible changes to the heavy body’s velocity. But you can also use it to allow two bodies to pass right through each other.

So what is a bitmask? A bitmask is a multi-digit binary number. Something like this: 1011 1000. Not so complicated at all.

But why are they useful? Well, they allow you to get state information out of a binary number and give you the ability to change a specific bit in a binary number to set a specific state. You can do this with the binary operators AND and OR, like so:

Bitmask power :)

This allows you to store a lot of information in a really compact way using just one variable and still be able to access and manipulate the stored information.

Note: Bit masks?!? In case you’ve never worked with bit masks, don’t panic! At first glance they might look complicated, but they are really useful.
Bitmask power :)
Bitmask power :)

So how do you do all this in code?

First, create constants for the different categories. Do this by adding the following lines below the other constants for the category names in MyScene.m:

static const uint32_t ballCategory  = 0x1 << 0;  // 00000000000000000000000000000001
static const uint32_t bottomCategory = 0x1 << 1; // 00000000000000000000000000000010
static const uint32_t blockCategory = 0x1 << 2;  // 00000000000000000000000000000100
static const uint32_t paddleCategory = 0x1 << 3; // 00000000000000000000000000001000

The above defines four categories. You do this by setting the last bit to 1 and all other bits to zero. Then using the << operator you shift this bit to the left. As a result, each of the category constants has only one bit set to 1 and the position of the 1 in the binary number is unique across the four categories.

For now you only need the category for the bottom of the screen and the ball, but you should set up the others anyway as you will probably need them later on as you expand the gameplay.

Once you have the constants in place, create a physics body that stretches across the bottom of the screen. Try to do this by yourself since this uses principles you've already learnt when creating the barriers around the screen edges. (Name the node containing the physics body bottom since you’ll be configuring that node in later steps.)

[spoiler title=”Create an edge-based body that covers the bottom of the screen”]
Add the following to initWithSize: in MyScene.m:

    CGRect bottomRect = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, 1);
    SKNode* bottom = [SKNode node];
    bottom.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:bottomRect];
    [self addChild:bottom];

[/spoiler]

With the preparations out of the way, let’s get to the meat of establishing contact. First, set up the categoryBitMasks for the bottom, ball, and paddle by adding the following code to initWithSize::

    bottom.physicsBody.categoryBitMask = bottomCategory;
    ball.physicsBody.categoryBitMask = ballCategory;
    paddle.physicsBody.categoryBitMask = paddleCategory;

This is really straightforward code. It simply assigns the constants you created earlier to the corresponding physics body’s categoryBitMask.

Now, set up the contactTestBitMask by adding this (again to initWithSize:):

    ball.physicsBody.contactTestBitMask = bottomCategory;

For now you only want to be notified when the ball makes contact with the bottom of the screen. Therefore, you set the contactTestBitMask to bottomCategory.

Next, you need to create an SKPhysicsContactDelegate. As this is a rather simple game, you will just make MyScene the delegate for all contacts.

Go to MyScene.h and change this line:

@interface MyScene : SKScene

To this line:

@interface MyScene : SKScene<SKPhysicsContactDelegate>

This makes it official: MyScene is now an SKPhysicsContactDelegate and will receive collision notifications for all configured physics bodies. Hurrah!

Now you need to set MyScene as the delegate in physicsWorld. So, add this to initWithSize: in MyScene.m

    self.physicsWorld.contactDelegate = self;

Setting up the SKPhysicsContactDelegate

Setting up the SKPhysicsContactDelegate

Finally, you need to implement didBeginContact: to handle the collisions. Add the method to MyScene.m:

-(void)didBeginContact:(SKPhysicsContact*)contact {
    // 1 Create local variables for two physics bodies
    SKPhysicsBody* firstBody;
    SKPhysicsBody* secondBody;
    // 2 Assign the two physics bodies so that the one with the lower category is always stored in firstBody
    if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {
        firstBody = contact.bodyA;
        secondBody = contact.bodyB;
    } else {
        firstBody = contact.bodyB;
        secondBody = contact.bodyA;
    }
    // 3 react to the contact between ball and bottom
    if (firstBody.categoryBitMask == ballCategory && secondBody.categoryBitMask == bottomCategory) {
        //TODO: Replace the log statement with display of Game Over Scene
        NSLog(@"Hit bottom. First contact has been made.");
    }
}

This code might appear a little weird to you, so let me walk you through.

  1. Create two local variables to hold the two physics bodies involved in the collision.
  2. Check the two bodies that collided to see which has the lower categoryBitmask. You then store them into the local variables, so that the body with the lower category is always stored in firstBody. This will save you quite some effort when reacting to contacts between specific categories.
  3. Profit from the sorting that you did just before. You only need to check whether firstBody is in the ballCategory and whether secondBody is in the bottomCategory to figure out that the ball has touched the bottom of the screen, as you already know that secondBody could not possibly be in the ballCategory if firstBody is in the bottomCategory (because bottomCategory has a higher bit mask than ballCategory). For now react with a simple log message.

It’s time to try out your code again. Build and run your game again and if you’ve done everything correctly, you should see the log message in the console every time the ball misses the paddle and hits the bottom of the screen. Like this:

First contact has been made :)

First contact has been made :)
Barbara Reichart

Contributors

Barbara Reichart

Author

Over 300 content creators. Join our team.