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

Learn how to make a game like Space Invaders in this 2-part Sprite Kit tutorial! By .

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

Controlling the Invaders' Direction

Adding the following code just after #pragma mark - Invader Movement Helpers:

-(void)determineInvaderMovementDirection {
    //1
    __block InvaderMovementDirection proposedMovementDirection = self.invaderMovementDirection;

    //2
    [self enumerateChildNodesWithName:kInvaderName usingBlock:^(SKNode *node, BOOL *stop) {
        switch (self.invaderMovementDirection) {
            case InvaderMovementDirectionRight:
                //3
                if (CGRectGetMaxX(node.frame) >= node.scene.size.width - 1.0f) {
                    proposedMovementDirection = InvaderMovementDirectionDownThenLeft;
                    *stop = YES;
                }
                break;
            case InvaderMovementDirectionLeft:
                //4
                if (CGRectGetMinX(node.frame) <= 1.0f) {
                    proposedMovementDirection = InvaderMovementDirectionDownThenRight;
                    *stop = YES;
                }
                break;
            case InvaderMovementDirectionDownThenLeft:
                //5
                proposedMovementDirection = InvaderMovementDirectionLeft;
                *stop = YES;
                break;
            case InvaderMovementDirectionDownThenRight:
                //6
                proposedMovementDirection = InvaderMovementDirectionRight;
                *stop = YES;
                break;
            default:
                break;
        }
    }];

    //7
    if (proposedMovementDirection != self.invaderMovementDirection) {
        self.invaderMovementDirection = proposedMovementDirection;
    }
}

Here's what's going on in the above code:

  1. Since local variables accessed by a block are by default const (that is, they cannot be changed), you must qualify proposedMovementDirection with __block so that you can modify it in //2.
  2. Loop over all the invaders in the scene and invoke the block with the invader as an argument.
  3. If the invader's right edge is within 1 point of the right edge of the scene, it's about to move offscreen. Set proposedMovementDirection so that the invaders move down then left. You compare the invader's frame (the frame that contains its content in the scene's coordinate system) with the scene width. Since the scene has an anchorPoint of (0, 0) by default, and is scaled to fill its parent view, this comparison ensures you're testing against the view's edges.
  4. If the invader's left edge is within 1 point of the left edge of the scene, it's about to move offscreen. Set proposedMovementDirection so that invaders move down then right.
  5. If invaders are moving down then left, they've already moved down at this point, so they should now move left. How this works will become more obvious when you integrate determineInvaderMovementDirection with moveInvadersForUpdate:.
  6. If the invaders are moving down then right, they've already moved down at this point, so they should now move right.
  7. If the proposed invader movement direction is different than the current invader movement direction, update the current direction to the proposed direction.

Add the following code to determineInvaderMovementDirection within moveInvadersForUpdate:, immediately after the conditional check of self.timeOfLastMove:

[self determineInvaderMovementDirection];

Why is it important that you add the invocation of determineInvaderMovementDirection only after the check on self.timeOfLastMove? That's because you want the invader movement direction to change only when the invaders are actually moving. Invaders only move when the check on self.timeOfLastMove passes — i.e., the conditional expression is true.

What would happen if you added the new line of code above as the very first line of code in moveInvadersForUpdate:? If you did that, then there would be two bugs:

  • You'd be trying to update the movement direction way too often -- 60 times per second -- when you know it can only change at most once per second.
  • The invaders would never move down, as the state transition from InvaderMovementDirectionDownThenLeft to InvaderMovementDirectionLeft would occur without an invader movement in between. The next invocation of moveInvadersForUpdate: that passed the check on self.timeOfLastMove would be executed with self.invaderMovementDirection == InvaderMovementDirectionLeft and would keep moving the invaders left, skipping the down move. A similar bug would exist for InvaderMovementDirectionDownThenRight and InvaderMovementDirectionRight.

Build and run your app; you'll see the invaders moving as expected across and down the screen, as below:

Moving right

Invaders moving right

Moving down then left

Invaders moving down then left

Moving left

Invaders moving left

Note: You might have noticed that the invaders' movement is jerky. That's a consequence of your code only moving invaders once per second — and moving them a decent distance at that. But the movement in the original game was jerky, so keeping this feature helps your game seem more authentic.

Adding Motion to your Ship

Good news: your supervisors can see the invaders moving now and have decided that your ship needs a propulsion system! To be effective, any good propulsion system needs a good control system. In other words, how do you, the ship's pilot, tell the ship's propulsion system what to do?

The important thing to remember about mobile games is the following:

Mobile games are not desktop/arcade games and desktop/arcade controls don't port well to mobile.

In a desktop or arcade version of Space Invaders, you'd have a physical joystick and fire button to move your ship and shoot invaders. Such is not the case on a mobile device such as an iPhone or iPad.

Some games attempt to use virtual joysticks or virtual D-pads but these rarely work well, in my opinion.

Think about how you use your iPhone most often: holding it with one hand. That leaves only one hand to tap/swipe/gesture on the screen.

Keeping the ergonomics of holding your iPhone with one hand in mind, consider several potential control schemes for moving your ship and firing your laser cannon:

Single-tap to move, double-tap to fire:

Suppose you single-tapped on the left side of the ship to move it left, single-tapped on the right of the ship to move it right, and double-tapped to make it fire. This wouldn't work well for a couple of reasons.

First, recognizing both single-taps and double-taps in the same view requires you to delay recognition of the single-tap until the double-tap fails or times out. When you're furiously tapping the screen, this delay will make the controls unacceptably laggy. Second, single-taps and double-taps might sometimes get confused, both by you, the pilot, and by the code. Third, the ship movement single-taps won't work well when your ship is near the extreme left- or right-edge of the screen. Scratch that control scheme!

Swipe to move, single-tap to fire:

This approach is a little better. Single-tapping to fire your laser cannon makes sense as both are discrete actions: one tap equals one blast from your canon. It's intuitive. But what about using swipes to move your ship?

This won't work because swipes are considered a discrete gesture. In other words, either you swiped or you didn't. Using the length of a swipe to proportionally control the amount of left or right thrust applied to your ship breaks your user's mental model of what swipes mean and the way they function. In all other apps, swipes are discrete and the length of a swipe is not considered meaningful. Scratch this control scheme as well.

Tilt your device left/right to move, single-tap to fire:

It's already been established that a single-tap to fire works well. But what about tilting your device left and right to move your ship left and right? This is your best option, as you're already holding your iPhone in the palm of your hand and tilting your device to either side merely requires you to twist your wrist a bit. You have a winner!

Now that you've settled on the control scheme, you'll first tackle tilting your device to move your ship.