How To Make a Game Like Space Invaders with Sprite Kit Tutorial: Part 2

Joel Shapiro
Learn how to make a game like Space Invaders!

Learn how to make a game like Space Invaders!

Note from Ray: This is a brand new Sprite Kit tutorial released as part of the iOS 7 Feast. Enjoy!

Welcome back to our 2-part Sprite Kit tutorial that teaches you how to make a game like Space Invaders!

In the first part, you created the foundation of the game. So far, you’ve added the invaders, your ship, and a Heads Up Display (HUD) to your game. You also coded the logic to make the invaders move automatically and to make your ship move as you tilted your device.

In this second and final part, you’ll add the ability for your ship and the aliens to fire on each other and blow each other up! You’ll also polish your game by adding sound effects and realistic images to replace the colored rectangles that currently serve as place holders for the invaders and your ship.

This tutorial picks up where the first part left off. If you don’t have the project already, you can download the example project where we left things off.

All right, it’s time to blow up some invaders!

Making Your Ship Fire its Laser Cannon

First, think about how you want your scene to fire the ship’s laser cannon. You decided to use single-taps to fire the cannon. But how should you detect these single taps?

You have two obvious choices:

  1. UITapGestureRecognizer – Create one and attach it to your scene’s view.
  2. touchesEnded:withEvent: – Your scene is a subclass of UIResponder; therefore, you could use this to detect touches directly.

The second approach is the best choice in this situation: touchesEnded:withEvent:. The first approach gets a bit tricky when you need to detect and handle touches differently in the various scene nodes of your game, since you can only specify a single callback selector for the UITapGestureRecognizer on the scene’s view. The extra work to get this working properly just isn’t worth it in this simple case.

Since all SKNode nodes (including SKScene) can handle touches directly via touchesEnded:withEvent:, the second choice is a much more natural approach to handling node-specific touches — and will pay off when you develop games with more complex tap handling.

Now that you’re going to detect user taps in your scene’s touchesEnded:withEvent: method, what should you do inside that method?

Taps can happen at any point during the gameplay. Contrast that with the way your scene changes: — at discrete intervals from within the update: method. So how can you save up taps detected at any time in touchesEnded:withEvent: and process them later in update: when it’s invoked by the Sprite Kit game loop?

The answer is a queue! You’re going to use a simple NSMutableArray to store your taps in a FIFO (First In First Out) queue.

Add the following property to the class extension in GameScene.m:

@property (strong) NSMutableArray* tapQueue;

Now add the following lines to didMoveToView: right after the [self.motionManager startAccelerometerUpdates]; line:

self.tapQueue = [NSMutableArray array];
self.userInteractionEnabled = YES;

The above code initializes the tap queue to an empty array and ensures that user interactions are enabled for the scene so it can receive tap events.

Now, add the following code right after #pragma mark - User Tap Helpers:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // Intentional no-op
}
 
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    // Intentional no-op
}
 
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    // Intentional no-op
}
 
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch* touch = [touches anyObject];
    if (touch.tapCount == 1) [self.tapQueue addObject:@1];
}

The first three methods are just empty stubs; they’re added because Apple suggests doing so when you override touchesEnded:withEvent: without calling super.

The touchesEnded:withEvent: method itself is fairly simple. It just adds an entry to the queue. You don’t need a custom class to store the tap in the queue since all you need to know is that a tap occurred. Therefore, you can use any old object. Here, you use the integer 1 as a mnemonic for single tap (@1 is the new object-literal syntax that converts the literal 1 into an NSNumber object).

Since you know that the invaders will also eventually fire bullets at your ship, add the following code to the Custom Type Definitions section at the top of the file:

typedef enum BulletType {
    ShipFiredBulletType,
    InvaderFiredBulletType
} BulletType;

You’re going to use BulletType to share the same bullet code for both invaders and your ship. It appears that you and the invaders shop at the same ammunition stores! :]

Next, add the following code just below #define kHealthHudName @"healthHud":

#define kShipFiredBulletName @"shipFiredBullet"
#define kInvaderFiredBulletName @"invaderFiredBullet"
#define kBulletSize CGSizeMake(4, 8)

Now, add the following method to the Scene Setup and Content Creation section:

-(SKNode*)makeBulletOfType:(BulletType)bulletType {
    SKNode* bullet;
 
    switch (bulletType) {
        case ShipFiredBulletType:
            bullet = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:kBulletSize];
            bullet.name = kShipFiredBulletName;
            break;
        case InvaderFiredBulletType:
            bullet = [SKSpriteNode spriteNodeWithColor:[SKColor magentaColor] size:kBulletSize];
            bullet.name = kInvaderFiredBulletName;
            break;
        default:
            bullet = nil;
            break;
    }
 
    return bullet;
}

This method is relatively straightforward: it simply creates a rectangular colored sprite to represent a bullet and sets the name of the bullet so you can find it later in your scene.

Now, add the following methods to the #pragma mark - Bullet Helpers section:

-(void)fireBullet:(SKNode*)bullet toDestination:(CGPoint)destination withDuration:(NSTimeInterval)duration soundFileName:(NSString*)soundFileName {
    //1
    SKAction* bulletAction = [SKAction sequence:@[[SKAction moveTo:destination duration:duration],
                                                  [SKAction waitForDuration:3.0/60.0],
                                                  [SKAction removeFromParent]]];
    //2
    SKAction* soundAction  = [SKAction playSoundFileNamed:soundFileName waitForCompletion:YES];
    //3
    [bullet runAction:[SKAction group:@[bulletAction, soundAction]]];
    //4
    [self addChild:bullet];
}
 
-(void)fireShipBullets {
    SKNode* existingBullet = [self childNodeWithName:kShipFiredBulletName];
    //1
    if (!existingBullet) {
        SKNode* ship = [self childNodeWithName:kShipName];
        SKNode* bullet = [self makeBulletOfType:ShipFiredBulletType];
        //2
        bullet.position = CGPointMake(ship.position.x, ship.position.y + ship.frame.size.height - bullet.frame.size.height / 2);
        //3
        CGPoint bulletDestination = CGPointMake(ship.position.x, self.frame.size.height + bullet.frame.size.height / 2);
        //4
        [self fireBullet:bullet toDestination:bulletDestination withDuration:1.0 soundFileName:@"ShipBullet.wav"];
    }
}

Going through the code in fireBullet:toDestination:withDuration:soundFileName: step-by-step, you do the following:

  1. Create an SKAction that moves the bullet to the desired destination and then removes it from the scene. This sequence executes the individual actions consecutively — the next action only takes place after the previous action has completed. Hence the bullet is removed from the scene only after it has been moved.
  2. Play the desired sound to signal that the bullet was fired. All sounds are included in the starter project and iOS knows how to find and load them.
  3. Move the bullet and play the sound at the same time by putting them in the same group. A group runs its actions in parallel, not sequentially.
  4. Fire the bullet by adding it to the scene. This makes it appear onscreen and starts the actions.

Here’s what you do in fireShipBullets:

  1. Only fire a bullet if there isn’t one currently on-screen. It’s a laser cannon, not a laser machine gun — it takes time to reload!
  2. Set the bullet’s position so that it comes out of the top of the ship.
  3. Set the bullet’s destination to be just off the top of the screen. Since the x coordinate is the same as that of the bullet’s position, the bullet will fly straight up.
  4. Fire the bullet!

The decision in //1 to only allow one ship bullet on-screen at the same time is a gameplay decision, not a technical necessity. If your ship can fire thousands of bullets per minute, Space Invaders would be too easy. Part of the fun of your game is choosing your shots wisely and timing them to collide with invaders.

Your laser cannon is almost ready to fire!

Add the following to the Scene Update Helpers section:

-(void)processUserTapsForUpdate:(NSTimeInterval)currentTime {
    //1
    for (NSNumber* tapCount in [self.tapQueue copy]) {
        if ([tapCount unsignedIntegerValue] == 1) {
            //2
            [self fireShipBullets];
        }
        //3
        [self.tapQueue removeObject:tapCount];
    }
}

Let’s review the above code:

  1. Loop over a copy of your tapQueue; it must be a copy because it’s possible that you’ll modify the original tapQueue while this code is running, and modifying an array while looping over it is a big no-no.
  2. If the queue entry is a single-tap, handle it. As the developer, you clearly know that you only handle single taps for now, but it’s best to be defensive against the possibility of double-taps (or other actions) later.
  3. Remove the tap from the queue.

Note: processUserTapsForUpdate: completely consumes the queue of taps at each invocation. Combined with the fact that fireShipBullets will not fire another bullet if one is already onscreen, this emptying of the queue means that extra or rapid-fire taps will be ignored. Only the first tap needed to fire a bullet will matter.

Finally, add the following code as the first line in update::

[self processUserTapsForUpdate:currentTime];

This invokes processUserTapsForUpdate: during the update loop and processes any user taps.

Build your game, run, and tap away!

Player Bullets

Making Invaders Attack

Awesome, your ship can finally fire on those evil invaders! You’ll have them on the run soon enough.

But you’ve probably noticed that your bullets pass straight through invaders instead of blowing them up. That’s because your bullets aren’t yet smart enough to detect when they’ve hit an invader. You’re going to fix that in a moment.

First, you’ll make the invaders return fire by adding the code below to the Scene Update Helpers section:

-(void)fireInvaderBulletsForUpdate:(NSTimeInterval)currentTime {
    SKNode* existingBullet = [self childNodeWithName:kInvaderFiredBulletName];
    //1
    if (!existingBullet) {
        //2
        NSMutableArray* allInvaders = [NSMutableArray array];
        [self enumerateChildNodesWithName:kInvaderName usingBlock:^(SKNode *node, BOOL *stop) {
            [allInvaders addObject:node];
        }];
 
        if ([allInvaders count] > 0) {
            //3
            NSUInteger allInvadersIndex = arc4random_uniform([allInvaders count]);
            SKNode* invader = [allInvaders objectAtIndex:allInvadersIndex];
            //4
            SKNode* bullet = [self makeBulletOfType:InvaderFiredBulletType];
            bullet.position = CGPointMake(invader.position.x, invader.position.y - invader.frame.size.height/2 + bullet.frame.size.height / 2);
            //5
            CGPoint bulletDestination = CGPointMake(invader.position.x, - bullet.frame.size.height / 2);
            //6
            [self fireBullet:bullet toDestination:bulletDestination withDuration:2.0 soundFileName:@"InvaderBullet.wav"];
        }
    }
}

The central logic for the above method is as follows:

  1. Only fire a bullet if one’s not already on-screen.
  2. Collect all the invaders currently on-screen.
  3. Select an invader at random.
  4. Create a bullet and fire it from just below the selected invader.
  5. The bullet should travel straight down and move just off the bottom of the screen.
  6. Fire off the invader’s bullet.

Add the following line to the end of update::

[self fireInvaderBulletsForUpdate:currentTime];

This invocation of fireInvaderBulletsForUpdate: starts the invaders firing back at you.

Build, run, and you should see the invaders firing their purple bullets at your ship, as shown in the screenshot below:

Enemy Bullets

As a matter of game design, notice that the invaders’ bullets are purple while your ship’s bullets are green. This strong color contrast makes it easy to see the difference between bullets in the heat of battle. Also, you should hear a different sound when your ship fires versus when an invader fires. The use of different sounds is partly stylistic, to give your game rich audio and make it more immersive. But it’s also partly an accessibility issue since 7 – 10% of men and 0.5% – 1% women are color blind. The differentiation in sound effects will make your game more playable by those who are color blind.

Detecting When Bullets Hit Their Target

With all those bullets flying around on the screen it’s amazing that nothing blows up! That’s because your game has no hit detection. It needs to detect when your ship’s bullets hit an invader — and when an invader’s bullet hits your ship.

You could do this manually, comparing bullet/invader/ship positions at each update: invocation and checking for hits. But why not let Sprite Kit do the work for you?

Since you’re already using physics bodies, Sprite Kit’s physics engine can detect when one body hits another. For this, you’ll use contact detection — not collision detection. You’re not using physics to move bullets or invaders, so you’re not interested in the physical collisions between them. Contact detection merely detects when one physics body overlaps another in space; it doesn’t otherwise move or affect the bodies in contact.

Some games have many distinct types of physics bodies and are not interested in contact between all types of physics bodies. Sprite Kit will only check for contact between those categories of physics bodies that you tell it to check.

This is both a speed optimization and a correctness constraint, as some types of contact may not be desired. Controlling which physics bodies are checked for contact begins by defining category bitmasks.

Add the following code to the Custom Type Definitions section:

static const u_int32_t kInvaderCategory            = 0x1 << 0;
static const u_int32_t kShipFiredBulletCategory    = 0x1 << 1;
static const u_int32_t kShipCategory               = 0x1 << 2;
static const u_int32_t kSceneEdgeCategory          = 0x1 << 3;
static const u_int32_t kInvaderFiredBulletCategory = 0x1 << 4;

These strange-looking constants are bitmasks. A bitmask is basically a way of stuffing multiple on/off variables into a single 32-bit unsigned integer. A bitmask can have 32 distinct values when stored as a u_int32_t. Each of these five categories defines a type of physics body. Notice how the number to the right of the << operator is different in each case; that guarantees each bitmask is unique and distinguishable from the others.

Add the following code to createContent right before the [self setupInvaders]; line:

self.physicsBody.categoryBitMask = kSceneEdgeCategory;

This new code sets the category for the physics body of your scene.

Add the following code to makeShip right before the return ship; line to set up the categories for your ship:

//1
ship.physicsBody.categoryBitMask = kShipCategory;
//2
ship.physicsBody.contactTestBitMask = 0x0;
//3
ship.physicsBody.collisionBitMask = kSceneEdgeCategory;

Here's the breakdown of the above code:

  1. Set the ship's category.
  2. Don't detect contact between the ship and other physics bodies.
  3. Do detect collisions between the ship and the scene's outer edges.

Note:You didn't need to set the ship's collisionBitMask before because only your ship and the scene had physics bodies. The default collisionBitMask of "all" was sufficient in that case. Since you'll be adding physics bodies to invaders next, setting your ship's collisionBitMask precisely ensures that your ship will only collide with the sides of the scene and won't also collide with invaders.

While you're at it, you should set the category for the invaders since this will help detect collisions between your ship's bullets and the invaders.

Add the following to the end of makeInvaderOfType: right before the return invader; line:

invader.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:invader.frame.size];
invader.physicsBody.dynamic = NO;
invader.physicsBody.categoryBitMask = kInvaderCategory;
invader.physicsBody.contactTestBitMask = 0x0;
invader.physicsBody.collisionBitMask = 0x0;

This code gives your invader a physics body and identifies it as an invader using kInvaderCategory. It also indicates that you don't want invaders to contact or collide with other entities.

Your next step is to categorize bullets and set their contact and collision masks.

Add the following to the end of the first case statement in makeBulletOfType: right before the break:

bullet.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:bullet.frame.size];
bullet.physicsBody.dynamic = YES;
bullet.physicsBody.affectedByGravity = NO;
bullet.physicsBody.categoryBitMask = kShipFiredBulletCategory;
bullet.physicsBody.contactTestBitMask = kInvaderCategory;
bullet.physicsBody.collisionBitMask = 0x0;

The above code identifies ship-fired bullets as such and tells Sprite Kit to check for contact between ship-fired bullets and invaders, but that collisions should be ignored.

That takes care of the ship's bullets — now on to the invaders' bullets!

Add the following code block to the end of the second case statement in makeBulletOfType:, just before the break:

bullet.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:bullet.frame.size];
bullet.physicsBody.dynamic = YES;
bullet.physicsBody.affectedByGravity = NO;
bullet.physicsBody.categoryBitMask = kInvaderFiredBulletCategory;
bullet.physicsBody.contactTestBitMask = kShipCategory;
bullet.physicsBody.collisionBitMask = 0x0;

This code is similar to the previous block: it identifies invader-fired bullets as such and tells Sprite Kit to check for contact between invader-fired bullets and your ship, and again, ignores the collision aspect.

Note: In order for contact detection to work, the ship-fired bullets must be defined as dynamic by setting bullet.physicsBody.dynamic = YES. If not, Sprite Kit won't check for contact between these bullets and the static invaders as their definition is invader.physicsBody.dynamic = NO. Invaders are static because they aren't moved by the physics engine. Sprite Kit won't check for contact between two static bodies, so if you need to check for contact between two categories of physics bodies, at least one of the categories must have a dynamic physics body.

You may be wondering why the contactTestBitMask values are not symmetrical. For example, why are you setting an invader's contactTestBitMastk = 0x0 but a ship-fired bullet's contactTestBitMask = kInvaderCategory?

The reason is that when Sprite Kit checks for contact between any two physics bodies A and B, only one of the bodies needs to declare that it should test for contact with the other, not both. As long as either A declares that it can contact with B, or B declares that it can contact with A, contact will be detected. It's not necessary for both bodies to declare that they should test for contact with the other.

Setting the contactTestBitMask on only one type of body like you've done seems more manageable. You might prefer to set contactTestBitMask values on both types of bodies, and that's fine, as long as you're consistent in choosing one approach or the other.

With these changes, your game's physics engine will detect contact between ship-fired bullets and the invaders, and between invader-fired bullets and your ship. But how does the physics engine inform your game of these contacts?

The answer is to use SKPhysicsContactDelegate.

Implementing the Physics Contact Delegate Methods

Open GameScene.h and modify the @interface line to look like the following:

@interface GameScene : SKScene <SKPhysicsContactDelegate>

This declares your scene as a delegate for the physics engine. The didBeginContact: method of SKPhysicsContactDelegate executes each time two physics bodies make contact, based on how you set your physics bodies' categoryBitMask and contactTestBitMask. You'll implement didBeginContact: in just a moment.

Much like taps, contact can happen at any time. Consequently, didBeginContact: can execute at any time. But in keeping with your discrete time ticks, you should only process contact during those ticks when update: is called. So, just like taps, you'll create a queue to store contacts until they can be processed via update:.

Switch back to GameScene.m and add the following new property to the class extension at the top:

@property (strong) NSMutableArray* contactQueue;

Now add the following code to the end of didMoveToView:, right after the self.userInteractionEnabled = YES; line:

self.contactQueue = [NSMutableArray array];
self.physicsWorld.contactDelegate = self;

This just initializes an empty contact queue and sets the scene as the contact delegate of the physics engine.

Next, add this method to the #pragma mark - Physics Contact Helpers section:

-(void)didBeginContact:(SKPhysicsContact *)contact {
    [self.contactQueue addObject:contact];
}

This method simply records the contact in your contact queue to handle later when update: executes.

Still in the same section, add the following method:

-(void)handleContact:(SKPhysicsContact*)contact {
    //1
    // Ensure you haven't already handled this contact and removed its nodes
    if (!contact.bodyA.node.parent || !contact.bodyB.node.parent) return;
 
    NSArray* nodeNames = @[contact.bodyA.node.name, contact.bodyB.node.name];
    if ([nodeNames containsObject:kShipName] && [nodeNames containsObject:kInvaderFiredBulletName]) {
        //2
        // Invader bullet hit a ship
        [self runAction:[SKAction playSoundFileNamed:@"ShipHit.wav" waitForCompletion:NO]];
        [contact.bodyA.node removeFromParent];
        [contact.bodyB.node removeFromParent];
    } else if ([nodeNames containsObject:kInvaderName] && [nodeNames containsObject:kShipFiredBulletName]) {
        //3
        // Ship bullet hit an invader
        [self runAction:[SKAction playSoundFileNamed:@"InvaderHit.wav" waitForCompletion:NO]];
        [contact.bodyA.node removeFromParent];
        [contact.bodyB.node removeFromParent];
    }
}

This code is relatively straightforward, and explained below:

  1. Don't allow the same contact twice.
  2. If an invader bullet hits your ship, remove your ship and the bullet from the scene and play a sound.
  3. If a ship bullet hits an invader, remove the invader and the bullet from the scene and play a different sound.

Add the following method to the #pragma mark - Scene Update Helpers section::

-(void)processContactsForUpdate:(NSTimeInterval)currentTime {
    for (SKPhysicsContact* contact in [self.contactQueue copy]) {
        [self handleContact:contact];
        [self.contactQueue removeObject:contact];
    }
}

The above just drains the contact queue, calling handleContact: for each contact in the queue.

Add the following line to the very top of update: to call your queue handler:

[self processContactsForUpdate:currentTime];

Build and run you app, and start firing at those invaders!

Exchange Fire

Now, when your ship’s bullet hits an invader, the invader disappears from the scene and an explosion sound plays. In contrast, when an invader’s bullet hits your ship, the code removes your ship from the scene and a different explosion sound plays.

Depending on your playing skill (or lack thereof!), you may have to run a few times to see both invaders and your ship get destroyed. Just hit Command R to run again.

Updating Your Heads Up Display (HUD)

Your game looks good, but it’s lacking a certain something. There’s not much dramatic tension to your game. What’s the advantage of hitting an invader with your bullet if you don’t get credit? What’s the downside to being hit by an invader’s bullet if there’s no penalty?

You’ll rectify this by awarding score points for hitting invaders with your ship’s bullets, and by reducing your ship’s health when it gets hit by an invader’s bullet.

Add the following properties to the class extension:

@property NSUInteger score;
@property CGFloat shipHealth;

Your ship’s health starts at 100% but you will store it as a number ranging from 0 to 1.

Add the following line to setupShip as the last line in the method:

self.shipHealth = 1.0f;

The above sets your ship’s initial health.

Now, replace the following line in setupHud:

healthLabel.text = [NSString stringWithFormat:@"Health: %.1f%%", 100.0f];

With this:

healthLabel.text = [NSString stringWithFormat:@"Health: %.1f%%", self.shipHealth * 100.0f];

The new line sets the initial HUD text based on your ship’s actual health value instead of a static value of 100.

Next, add the following two methods to the #pragma mark - HUD Helpers section:

-(void)adjustScoreBy:(NSUInteger)points {
    self.score += points;
    SKLabelNode* score = (SKLabelNode*)[self childNodeWithName:kScoreHudName];
    score.text = [NSString stringWithFormat:@"Score: %04u", self.score];
}
 
-(void)adjustShipHealthBy:(CGFloat)healthAdjustment {
    //1
    self.shipHealth = MAX(self.shipHealth + healthAdjustment, 0);
 
    SKLabelNode* health = (SKLabelNode*)[self childNodeWithName:kHealthHudName];
    health.text = [NSString stringWithFormat:@"Health: %.1f%%", self.shipHealth * 100];
}

These methods are fairly straightforward: update the score and the score label, and update the ship’s health and the health label. //1 merely ensures that the ship’s health doesn’t go negative.

The final step is to call these methods at the right time during gameplay. Replace handleContact: with the following updated version:

-(void)handleContact:(SKPhysicsContact*)contact {
    // Ensure you haven't already handled this contact and removed its nodes
    if (!contact.bodyA.node.parent || !contact.bodyB.node.parent) return;
 
    NSArray* nodeNames = @[contact.bodyA.node.name, contact.bodyB.node.name];
    if ([nodeNames containsObject:kShipName] && [nodeNames containsObject:kInvaderFiredBulletName]) {
        // Invader bullet hit a ship
        [self runAction:[SKAction playSoundFileNamed:@"ShipHit.wav" waitForCompletion:NO]];
        //1
        [self adjustShipHealthBy:-0.334f];
        if (self.shipHealth <= 0.0f) {
            //2
            [contact.bodyA.node removeFromParent];
            [contact.bodyB.node removeFromParent];
        } else {
            //3
            SKNode* ship = [self childNodeWithName:kShipName];
            ship.alpha = self.shipHealth;
            if (contact.bodyA.node == ship) [contact.bodyB.node removeFromParent];
            else [contact.bodyA.node removeFromParent];
        }
    } else if ([nodeNames containsObject:kInvaderName] && [nodeNames containsObject:kShipFiredBulletName]) {
        // Ship bullet hit an invader
        [self runAction:[SKAction playSoundFileNamed:@"InvaderHit.wav" waitForCompletion:NO]];
        [contact.bodyA.node removeFromParent];
        [contact.bodyB.node removeFromParent];
        //4
        [self adjustScoreBy:100];
    }
}

Here’s what’s changed in the method:

  1. Adjust the ship’s health when it gets hit by an invader’s bullet.
  2. If the ship’s health is zero, remove the ship and the invader’s bullet from the scene.
  3. If the ship’s health is greater than zero, only remove the invader’s bullet from the scene. Dim the ship’s sprite slightly to indicate damage.
  4. When an invader is hit, add 100 points to the score.

The above also explains why you store the ship’s health as a value between 0 and 1, even though your health starts at 100. Since alpha values range from 0 to 1, you can use the ship’s health value as the alpha value for for your ship to indicate progressive damage. That’s pretty handy!

Build and run your game again; you should see the score change when your bullets hit an invader; as well, you should see your ship’s health change when your ship is hit, as below:

Scores Updating

Polishing Your Invader and Ship Images

You’ve been incredibly patient working with these less-than-menacing red, green, blue and magenta rectangles. Keeping the visuals simple has worked well because it allowed you to focus ruthlessly on getting your game logic correct.

Now you’ll add some actual image sprites to make your game much more realistic — and more fun to play!

Replace makeInvaderOfType: with the following two methods:

-(NSArray*)loadInvaderTexturesOfType:(InvaderType)invaderType {
    NSString* prefix;
    switch (invaderType) {
        case InvaderTypeA:
            prefix = @"InvaderA";
            break;
        case InvaderTypeB:
            prefix = @"InvaderB";
            break;
        case InvaderTypeC:
        default:
            prefix = @"InvaderC";
            break;
    }
    //1
    return @[[SKTexture textureWithImageNamed:[NSString stringWithFormat:@"%@_00.png", prefix]],
             [SKTexture textureWithImageNamed:[NSString stringWithFormat:@"%@_01.png", prefix]]];
}
 
-(SKNode*)makeInvaderOfType:(InvaderType)invaderType {
    NSArray* invaderTextures = [self loadInvaderTexturesOfType:invaderType];
    //2
    SKSpriteNode* invader = [SKSpriteNode spriteNodeWithTexture:[invaderTextures firstObject]];
    invader.name = kInvaderName;
    //3
    [invader runAction:[SKAction repeatActionForever:[SKAction animateWithTextures:invaderTextures timePerFrame:self.timePerMove]]];
 
    invader.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:invader.frame.size];
    invader.physicsBody.dynamic = NO;
    invader.physicsBody.categoryBitMask = kInvaderCategory;
    invader.physicsBody.contactTestBitMask = 0x0;
    invader.physicsBody.collisionBitMask = 0x0;
 
    return invader;
}

Here’s what the new code does:

  1. Loads a pair of sprite images — InvaderA_00.png and InvaderA_01.png — for each invader type and creates SKTexture objects from them.
  2. Uses the first such texture as the sprite’s base image.
  3. Animates these two images in a continuous animation loop.

All of the images were included in the starter project and iOS knows how to find and load them, so there’s nothing left to do here.

Build and run your app; you should see something similar to the screenshot below:

Invader sprite images

Looks pretty cool doesn’t it? Next, you’ll replace your blocky green ship with a much more retro and stylish looking version.

Replace makeShip with the following:

-(SKNode*)makeShip {
    //1
    SKSpriteNode* ship = [SKSpriteNode spriteNodeWithImageNamed:@"Ship.png"];
    ship.name = kShipName;
    //2
    ship.color = [UIColor greenColor];
    ship.colorBlendFactor = 1.0f;
    ship.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ship.frame.size];
    ship.physicsBody.dynamic = YES;
    ship.physicsBody.affectedByGravity = NO;
    ship.physicsBody.mass = 0.02;
    ship.physicsBody.categoryBitMask = kShipCategory;
    ship.physicsBody.contactTestBitMask = 0x0;
    ship.physicsBody.collisionBitMask = kSceneEdgeCategory;
 
    return ship;
}

This code looks a bit different. Here’s what’s going on:

  1. Your ship sprite is now constructed from an image.
  2. Originally, the ship image is white, just like the invader images. But the code sets the sprite color to make the image green. Effectively this blends the green color with the sprite image.

Build and run your game; you should see your official-looking green ship appear as below:

Ship sprite image

Play your game for a while — what do you notice? Although you can blast happily away at the invaders, there’s no clear victory or defeat. It’s not much of a space war, is it?

Implementing the End Game

Think about how your game should end. What are the conditions that will lead to a game being over?

  • Your ship’s health drops to zero.
  • You destroy all the invaders.
  • The invaders get too close to Earth.

You’ll now add checks for each of the above conditions.

First, add the following constant to the #pragma mark - Custom Type Definitions section, underneath the definition for kShipSize::

#define kMinInvaderBottomHeight 2 * kShipSize.height

The above defines the height at which the invaders are considered to have invaded Earth.

Next, add the following import to the #import section at the top of the file:

#import "GameOverScene.h"

The above imports the header for a scene named GameOverScene which is already present in the starter project.

Next, add the following new property to the class extension:

@property BOOL gameEnding;

That sets everything up for the various game over scenarios.

Now, add the following two methods to the #pragma mark - Game End Helpers section:

-(BOOL)isGameOver {
    //1
    SKNode* invader = [self childNodeWithName:kInvaderName];
 
    //2
    __block BOOL invaderTooLow = NO;
    [self enumerateChildNodesWithName:kInvaderName usingBlock:^(SKNode *node, BOOL *stop) {
        if (CGRectGetMinY(node.frame) <= kMinInvaderBottomHeight) {
            invaderTooLow = YES;
            *stop = YES;
        }
    }];
 
    //3
    SKNode* ship = [self childNodeWithName:kShipName];
 
    //4
    return !invader || invaderTooLow || !ship;
}
 
-(void)endGame {
    //1
    if (!self.gameEnding) {
        self.gameEnding = YES;
        //2
        [self.motionManager stopAccelerometerUpdates];
        //3
        GameOverScene* gameOverScene = [[GameOverScene alloc] initWithSize:self.size];
        [self.view presentScene:gameOverScene transition:[SKTransition doorsOpenHorizontalWithDuration:1.0]];
    }
}

Here’s what’s happening in the first method, which checks to see if the game is over:

  1. Get all invaders that remain in the scene.
  2. Iterate through the invaders to check if any invaders are too low.
  3. Get a pointer to your ship: if the ship’s health drops to zero, then the player is considered dead and the player ship will be removed from the scene. In this case, you’d get a nil value indicating that there is no player ship.
  4. Return whether your game is over or not. If there are no more invaders, or an invader is too low, or your ship is destroyed, then the game is over.

The second method actually ends the game and displays the game over scene. Here’s what the code does:

  1. End your game only once. Otherwise, you’ll try to display the game over scene multiple times and this would be a definite bug.
  2. Stop accelerometer updates.
  3. Show the GameOverScene. You can inspect GameOverScene.m for the details, but it’s a basic scene with a simple “Game Over” message. The scene will start another game if you tap on it.

Add the following line as the first line of code in update::

    if ([self isGameOver]) [self endGame];

The above checks to see if the game is over every time the scene updates. If the game is over, then it displays the game over scene.

Build and run; blast away at the invaders until your game ends. Hopefully, you’ll destroy all of the invaders before they destroy you! Once your game ends, you should see a screen similar to the following:

Game over

Tap the game over scene and you should be able to play again!

One Last Thing: Polish and Fidelity

It’s a truism of game development that the last 20% of game development takes as long as the first 80%. When you’re working on your next game, it’s a good idea to start out iterating quickly with low-fidelity art assets (e.g. your colored squares) so you can quickly figure out if your game is fun to play.

If it’s not fun to play with colored squares, it’s not going to be fun to play with fancy art work, either! Nail down your gameplay and game logic first, then build out with fancy art assets and cool sound effects.

That being said, it’s essential that you polish your game before releasing it to the App Store. The App Store is a crowded market and spit and polish will distinguish your app from the competition. Try to add little animations, storylines and a dash of cute factor that will delight your users. Also, consider being true to the game if you’re remaking a classic.

If you’re a fan of Space Invaders, you’ll know that your remake is missing one important element. In the original game, the invaders march faster the closer they get to the bottom of the screen.

This was an artifact of the early CPU used to run the first Space Invaders game – the game loop ran faster and faster with fewer invaders because there was less work to do with each loop cycle. The game’s programmer, Tomohiro Nishikado, decided to leave this behavior in the game as a challenging game mechanic.

You’ll update your game to incorporate this game mechanic as well to please the retro gaming purists out there.

Add the following method to the #pragma mark - Invader Movement Helpers section:

-(void)adjustInvaderMovementToTimePerMove:(NSTimeInterval)newTimePerMove {
    //1
    if (newTimePerMove <= 0) return;
 
    //2
    double ratio = self.timePerMove / newTimePerMove;
    self.timePerMove = newTimePerMove;
 
    [self enumerateChildNodesWithName:kInvaderName usingBlock:^(SKNode *node, BOOL *stop) {
        //3
        node.speed = node.speed * ratio;
    }];
}

Let’s examine this code:

  1. Ignore bogus values — a value less than or equal to zero would mean infinitely fast or reverse movement, which doesn’t make sense.
  2. Set the scene’s timePerMove to the given value. This will speed up the movement of invaders within moveInvadersForUpdate:. Record the ratio of the change so you can adjust the node’s speed accordingly.
  3. Speed up the animation of invaders so that the animation cycles through its two frames more quickly. The ratio ensures that if the new time per move is 1/3 the old time per move, the new animation speed is 3 times the old animation speed. Setting the node’s speed ensures that all of the node’s actions run more quickly, including the action that animates between sprite frames.

Now, you need something to invoke this new method.

Modify determineInvaderMovementDirection as indicated by comments below:

...
case InvaderMovementDirectionDownThenLeft:
    proposedMovementDirection = InvaderMovementDirectionLeft;
    // Add the following line
    [self adjustInvaderMovementToTimePerMove:self.timePerMove * 0.8];
    *stop = YES;
    break;
case InvaderMovementDirectionDownThenRight:
    proposedMovementDirection = InvaderMovementDirectionRight;
    // Add the following line
    [self adjustInvaderMovementToTimePerMove:self.timePerMove * 0.8];
...

The new code simply reduces the time per move by 20% each time the invaders move down. This increases their speed by 25% (4/5 the move time means 5/4 the move speed).

Build and run your game, and watch the movement of the invaders; you should notice that those invaders move faster and faster as they get closer to the bottom of the screen:

Final Screen

This was a quick and easy code change that made your game that much more challenging and fun to play. If you’re going to save the Earth from invading hordes, you might as well do it right! Spending time on seemingly minor tweaks like this is what makes the difference between a good game and a GREAT game.

Where to Go From Here?

Here is the final example project from this Sprite Kit tutorial.

I encourage you to experiment with your SKInvaders game. Play with it, tweak it and see what you can do! Breaking your code, fixing it, then finally seeing an awesome new feature come to life is one of the many thrills of game development.

If you want a few ideas for how to tweak your game, here are some:

Add a victory or defeat message to your GameOverScene

Hint: Add a property to GameOverScene that stores a message. Think about how to pass/set that property to the scene and how you would display it on-screen.

Add a title scene to your game

Hint: Add another SKScene subclass (call it TitleScene) that is displayed first by your GameViewController. Tapping this scene should transition in your existing GameScene.

Add a streak bonus when your ship hits invaders with three bullets in a row

Hint: Add a property to your GameScene to track hits vs misses: increment it for hits, reset to zero for misses. When the player gets three consecutive hits, show a special “STREAK!” animation and play a special sound.

Animate invaders when hit by your bullets

Hint: Take a look at handleContact: and think about how you could use a sequence of SKAction actions to animate the invader that was hit. How might this complicate your game state? Would you need to mark these invaders as “dead” so that they would no longer be considered for future contacts or scoring while they were doing their “death animation”, but not yet removed from the scene?

Add a “boss” invader that only moves horizontally across the top of the screen

Hint: Add new constants for this invader’s name and category. Use a pixel art tool such as Pixen to draw your own “Boss Invader”. Make him mean! Think about where you need to add code to manage this new invader by looking at existing code that adds existing invaders and go from there.

Get rid of the nodes and FPS debug info in the scene

Hint: Think about where and when the GameScene was created and initialized.

Add a “player lives” feature instead of health

Hint: Currently, your game shows ship health. Instead, make the player lose a “life” each time their ship gets hit. End your game when they have no ships remaining. How will you show player lives on the screen?

Add a computer-generated voiceover to your game

Hint: Use a new feature in iOS 7 that makes your iPhone utter any NSString that you give it.

Add a High Score List

Hint: Keep track of player scores. Show players the high score list after each game. If you want to personalize your game further, allow them to enter their initials with each score. Do this locally without Game Center.

Add defense shields between your ship and the invaders

Hint: In the original game, there were “defensive shields” above the player’s ship that could absorb invader bullets. Each bullet that hit the shield would destroy part of the shield. As enough bullet hit a shield in the same area, channels would be carved through it that allowed bullets to pass through unhindered. The shields were evenly-spaced with gaps between them. The player could hide his or her ship underneath these shields.

Through this tutorial series you learned some new tricks about Sprite Kit by building a very cool classic game along the way. If you want to learn how to build more fun games like this, check out our brand new book iOS Games by Tutorials, where you’ll learn how to make five complete games from scratch!

Enjoy playing your new game; I look forward to hearing from you with comments below or questions in the forums!

Joel develops iOS apps, games and Ruby on Rails backends as the founder and CTO of RepublicOfApps. He publishes iOS Game Dev Weekly - a free weekly email newsletter with the best hand-picked iOS game dev links. You can follow him on Twitter as @SizzlerWA. His latest game is Don't Step the Wrong Number — an addicting game of speed and reflexes.

Other Items of Interest

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 19 total!

Swift Team

... 16 total!

iOS Team

... 30 total!

Android Team

... 15 total!

macOS Team

... 11 total!

Apple Game Frameworks Team

... 10 total!

Unity Team

... 11 total!

Articles Team

... 11 total!

Resident Authors Team

... 11 total!