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

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!

Space Invaders is one of the most important video games ever developed. Created by Tomohiro Nishikado and released in 1978 by Taito Corporation, it earned billions of dollars in revenue. It became a cultural icon, inspiring legions of non-geeks to take up video games as a hobby.

Space Invaders used to be played in big game cabinets in video arcades, chewing up our allowances one quarter at a time. When the Atari 2600 home video game console went to market, Space Invaders was the “killer app” that drove sales of Atari hardware.

In this tutorial, you’ll build an iOS version of Space Invaders using Sprite Kit, the 2D game framework newly introduced in iOS 7.

This tutorial assumes you are familiar with the basics of Sprite Kit. If you are completely new to Sprite Kit, you should go through our Sprite Kit tutorial for beginners first.

Also, you will need an iPhone or iPod Touch running iOS 7 and an Apple developer account in order to get the most out of this tutorial. That is because you will be moving the ship in this game using the accelerometer, which is not present on the iOS simulator. If you don’t have an iOS 7 device or developer account, you can still complete the tutorial — you just won’t be able to move your ship.

Without further ado, let’s get ready to blast some aliens!

An original Space Invaders arcade cabinet

An original Space Invaders arcade cabinet

Getting Started

Apple provides an XCode 5 template named Sprite Kit Game which is pretty useful if you want to create your next smash hit from scratch. However, in order to get you started quickly, download the starter project for this tutorial. It’s based on the Sprite Kit template and already has some of the more tedious work done for you.

Once you’ve downloaded and unzipped the project, navigate to the SKInvaders directory and double-click SKInvaders.xcodeproj to open the project in Xcode.

Build and run the project by selecting the Run button from the Xcode toolbar (the first button on the top left) or by using the keyboard shortcut Command + R. You should see the following screen appear on your device or your simulator:

First run - invaders are already watching you

Creepy – the invaders are watching you already! However, if you see the screen above, this means you’re ready to move forward.

The Role of GameScene

To complete your Space Invaders game, you’ll have to code several independent bits of game logic; this tutorial will serve as a great exercise in constructing and refining game logic. It will also reinforce your understanding of how Sprite Kit elements fit together to produce the action in a game.

Most of the action in your game takes place in the stubbed-out GameScene class. You’ll spend most of this tutorial filling out GameScene with your game code.

Let’s take a look at how the game is set up. Open GameViewController.m and scroll down to viewDidLoad. This method is key to all UIKit apps and runs after GameViewController loads its view into memory. It’s intended as a spot for you to further customize your app’s UI at runtime.

Take a look at the interesting parts of viewDidLoad below:

  - (void)viewDidLoad
  {
    // ... omitted ...
 
    SKView * skView = (SKView *)self.view;
 
    // ... omitted ...
 
    //1 Create and configure the scene.
    SKScene * scene = [GameScene sceneWithSize:skView.bounds.size];
    scene.scaleMode = SKSceneScaleModeAspectFill;
 
    //2 Present the scene.
    [skView presentScene:scene];
  }

The section of code shown above creates and displays the scene as follows:

  1. First, create the scene with the same dimensions as its containing view. scaleMode ensures that the scene is large enough to fill the entire view.
  2. Next, present the scene to draw it on-screen.

Once GameScene is on-screen, it takes over from GameViewController and drives the rest of your game.

Open GameScene.m and take a look at how it’s organized:

  #import "GameScene.h"
  #import "GameOverScene.h"
  #import <CoreMotion/CoreMotion.h>
 
  #pragma mark - Custom Type Definitions
 
  #pragma mark - Private GameScene Properties
 
  @interface GameScene ()
  @property BOOL contentCreated;
  @end
 
  @implementation GameScene
 
  #pragma mark Object Lifecycle Management
 
  #pragma mark - Scene Setup and Content Creation
 
  - (void)didMoveToView:(SKView *)view
  {
      if (!self.contentCreated) {
          [self createContent];
          self.contentCreated = YES;
      }
  }
 
  - (void)createContent
  {
    // ... omitted ...
  }
 
  #pragma mark - Scene Update
 
  - (void)update:(NSTimeInterval)currentTime
  {
  }
 
  #pragma mark - Scene Update Helpers
 
  #pragma mark - Invader Movement Helpers
 
  #pragma mark - Bullet Helpers
 
  #pragma mark - User Tap Helpers
 
  #pragma mark - HUD Helpers
 
  #pragma mark - Physics Contact Helpers
 
  #pragma mark - Game End Helpers
 
  @end

You’ll notice that there are a lot of #pragma mark - Something or Other type lines in the file. These are called compiler directives since they control the compiler. These particular pragmas are used solely to make the source file easier to navigate.

How do pragmas make source navigation easier, you ask? Notice the area in the bar next to GameScene.m that says “No Selection”, as below:

Where to click for the pragma menu in Xcode

If you click on “No Selection”, a little menu pops up, as so:

The Xcode pragma menu

Hey — that’s a list of all of your pragmas! Click on any pragma to jump to that section of the file. This feature doesn’t look like it has much value at present, but once you’ve added a bunch of invader-killing code, these pragmas will be a really…er… pragmatic way of navigating through your file! :]

Creating the Evil Invaders from Space

Before you start coding, take a moment to consider the GameScene class. When is it initialized and presented on screen? When is the best time to set up your scene with its content?

You might think the scene’s initializer, initWithSize: fits the bill, but the scene may not be fully configured or scaled at the time its initializer runs. It’s better to create a scene’s content once the scene has been presented by a view, since at that point the environment in which the scene operates is “ready to go.”

A view invokes the scene’s didMoveToView: method to present it to the world. Navigate to didMoveToView: and you’ll see the following:

  - (void)didMoveToView:(SKView *)view
  {
      if (!self.contentCreated) {
          [self createContent];
          self.contentCreated = YES;
      }
  }

This method simply invokes createContent using the BOOL property contentCreated to make sure you don’t create your scene’s content more than once. This property is defined in an Objective-C Class Extension found near the top of the file, as below:

    #pragma mark - Private GameScene Properties
 
    @interface GameScene ()
    @property BOOL contentCreated;
    @end

As the pragma points out, this class extension allows you to add “private” properties to your GameScene class, without revealing them to other external classes or code. You still get the benefit of using Objective-C properties, but your GameScene state is stored internally and can’t be modified by other external classes. As well, it doesn’t clutter the namespace of datatypes that your other classes see.

Just as you did in your private scene properties, you can define important constants as private definitions within your file. Navigate to #pragma mark - Custom Type Definitions and add the following code:

//1
typedef enum InvaderType {
    InvaderTypeA,
    InvaderTypeB,
    InvaderTypeC
} InvaderType;
 
//2
#define kInvaderSize CGSizeMake(24, 16)
#define kInvaderGridSpacing CGSizeMake(12, 12)
#define kInvaderRowCount 6
#define kInvaderColCount 6
//3
#define kInvaderName @"invader"

The above type definition and constant definitions take care of the following tasks:

  1. Define the possible types of invader enemies. You can use this in switch statements later when you need to do things such as displaying different sprite images for each enemy type. The typedef also makes InvaderType a formal Objective-C type that is type-checked for method arguments and variables. This ensures that you don’t pass the wrong method argument or assign it to the wrong variable.
  2. Define the size of the invaders and that they’ll be laid out in a grid of rows and columns on the screen.
  3. Define a name you’ll use to identify invaders when searching for them in the scene.

It’s good practice to define constants like this rather than using raw numbers like 6 (also known as “magic numbers”) or raw strings like @"invader" (“magic strings”) that are prone to typos. Imagine mistyping @"Invader" where you meant @"invader" and spending hours debugging to find that a simple typo messed everything up. Using constants like kInvaderRowCount and kInvaderName prevents frustrating bugs — and makes it clear to other programmers what these constant values mean.

All right, time to make some invaders! Add the following method to GameScene.m directly after createContent:

-(SKNode*)makeInvaderOfType:(InvaderType)invaderType {
    //1
    SKColor* invaderColor;
    switch (invaderType) {
        case InvaderTypeA:
            invaderColor = [SKColor redColor];
            break;
        case InvaderTypeB:
            invaderColor = [SKColor greenColor];
            break;
        case InvaderTypeC:
        default:
            invaderColor = [SKColor blueColor];
            break;
    }
 
    //2
    SKSpriteNode* invader = [SKSpriteNode spriteNodeWithColor:invaderColor size:kInvaderSize];
    invader.name = kInvaderName;
 
    return invader;
}

makeInvaderOfType:, as the name implies, creates an invader sprite of a given type. You take the following actions in the above code:

  1. Use the invaderType parameter to determine the color of the invader.
  2. Call the handy convenience method spriteNodeWithColor:size: of SKSpriteNode to allocate and initialize a sprite that renders as a rectangle of the given color invaderColor with size kInvaderSize.

Okay, so a colored block is not the most menacing enemy imaginable. It may be tempting to design invader sprite images and dream about all the cool ways you can animate them, but the best approach is to focus on the game logic first, and worry about aesthetics later.

Adding makeInvaderOfType: isn’t quite enough to display the invaders on the screen. You’ll need something to invoke makeInvaderOfType: and place the newly created sprites in the scene.

Still in GameScene.m add the following method directly after makeInvaderOfType::

-(void)setupInvaders {
    //1
    CGPoint baseOrigin = CGPointMake(kInvaderSize.width / 2, 180);
    for (NSUInteger row = 0; row < kInvaderRowCount; ++row) {
        //2
        InvaderType invaderType;
        if (row % 3 == 0)      invaderType = InvaderTypeA;
        else if (row % 3 == 1) invaderType = InvaderTypeB;
        else                   invaderType = InvaderTypeC;
 
        //3
        CGPoint invaderPosition = CGPointMake(baseOrigin.x, row * (kInvaderGridSpacing.height + kInvaderSize.height) + baseOrigin.y);
 
        //4
        for (NSUInteger col = 0; col < kInvaderColCount; ++col) {
            //5
            SKNode* invader = [self makeInvaderOfType:invaderType];
            invader.position = invaderPosition;
            [self addChild:invader];
            //6
            invaderPosition.x += kInvaderSize.width + kInvaderGridSpacing.width;
        }
    }
}

The above method lays out invaders in a grid of rows and columns. Each row contains only a single type of invader. The logic looks complicated, but if you break it down, it makes perfect sense:

  1. Loop over the rows.
  2. Choose a single InvaderType for all invaders in this row based on the row number.
  3. Do some math to figure out where the first invader in this row should be positioned.
  4. Loop over the columns.
  5. Create an invader for the current row and column and add it to the scene.
  6. Update the invaderPosition so that it’s correct for the next invader.

Now, you just need to display the invaders on the screen. Replace the current code in createContent with the following:

[self setupInvaders];

Build and run your app; you should see a bunch of invaders on the screen, as shown below:

You've added invaders

The rectangular alien overlords are here! :]

Create Your Valiant Ship

With those evil invaders on screen, your mighty ship can’t be far behind. Just as you did for the invaders, you first need to define a few constants.

Add the following code immediately below the #define kInvaderName line:

#define kShipSize CGSizeMake(30, 16)
#define kShipName @"ship"

kShipSize stores the size of the ship, and kShipName stores the name you will set on the sprite node, so you can easily look it up later.

Next, add the following two methods just after setupInvaders:

-(void)setupShip {
    //1
    SKNode* ship = [self makeShip];
    //2
    ship.position = CGPointMake(self.size.width / 2.0f, kShipSize.height/2.0f);
    [self addChild:ship];
}
 
-(SKNode*)makeShip {
    SKNode* ship = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:kShipSize];
    ship.name = kShipName;
    return ship;
}

Here’s the interesting bits of logic in the two methods above:

  1. Create a ship using makeShip. You can easily reuse makeShip later if you need to create another ship (e.g. if the current ship gets destroyed by an invader and the player has “lives” left).
  2. Place the ship on the screen. In Sprite Kit, the origin is at the lower left corner of the screen. The anchorPoint is based on a unit square with (0, 0) at the lower left of the sprite’s area and (1, 1) at its top right. Since SKSpriteNode has a default anchorPoint of (0.5, 0.5), i.e., its center, the ship’s position is the position of its center. Positioning the ship at kShipSize.height/2.0f means that half of the ship’s height will protrude below its position and half above. If you check the math, you’ll see that the ship’s bottom aligns exactly with the bottom of the scene.

To display your ship on the screen, add the following line to the end of createContent:

[self setupShip];

Build and run your app; and you should see your ship arrive on the scene, as below:

You've added your ship

Fear not, citizens of Earth! Your trusty spaceship is here to save the day!

Adding the Heads Up Display (HUD)

It wouldn’t be much fun to play Space Invaders if you didn’t keep score, would it? You’re going to add a heads-up display (or HUD) to your game. As a star pilot defending Earth, your performance is being monitored by your commanding officers. They’re interested in both your “kills” (score) and “battle readiness” (health).

Add the following constants at the top of GameScene.m, just below #define kShipName:

#define kScoreHudName @"scoreHud"
#define kHealthHudName @"healthHud"

Now, add your HUD by inserting the following method right after makeShip:

-(void)setupHud {
    SKLabelNode* scoreLabel = [SKLabelNode labelNodeWithFontNamed:@"Courier"];
    //1
    scoreLabel.name = kScoreHudName;
    scoreLabel.fontSize = 15;
    //2
    scoreLabel.fontColor = [SKColor greenColor];
    scoreLabel.text = [NSString stringWithFormat:@"Score: %04u", 0];
    //3
    scoreLabel.position = CGPointMake(20 + scoreLabel.frame.size.width/2, self.size.height - (20 + scoreLabel.frame.size.height/2));
    [self addChild:scoreLabel];
 
    SKLabelNode* healthLabel = [SKLabelNode labelNodeWithFontNamed:@"Courier"];
    //4
    healthLabel.name = kHealthHudName;
    healthLabel.fontSize = 15;
    //5
    healthLabel.fontColor = [SKColor redColor];
    healthLabel.text = [NSString stringWithFormat:@"Health: %.1f%%", 100.0f];
    //6
    healthLabel.position = CGPointMake(self.size.width - healthLabel.frame.size.width/2 - 20, self.size.height - (20 + healthLabel.frame.size.height/2));
    [self addChild:healthLabel];
}

This is boilerplate code for creating and adding text labels to a scene. The relevant bits are as follows:

  1. Give the score label a name so you can find it later when you need to update the displayed score.
  2. Color the score label green.
  3. Position the score label near the top left corner of the screen.
  4. Give the health label a name so you can reference it later when you need to update the displayed health.
  5. Color the health label red; the red and green indicators are common colors for these indicators in games, and they’re easy to differentiate in the middle of furious gameplay.
  6. Position the health label near the top right corner of the screen.

Add the following line to the end of createContent to call the setup method for your HUD:

[self setupHud];

Build and run your app; you should see the HUD in all of its red and green glory on your screen as shown below:

You've added your HUD

Invaders? Check. Ship? Check. HUD? Check. Now all you need is a little dynamic action to tie it all together!

Adding Motion to the Invaders

To render your game onto the screen, Sprite Kit uses a game loop which searches endlessly for state changes that require on-screen elements to be updated. The game loop does several things, but you’ll be interested in the mechanisms that update your scene. You do this by overriding the update: method, which you’ll find as a stub in your GameScene.m file.

When your game is running smoothly and renders 60 frames-per-second (iOS devices are hardware-locked to a max of 60 fps), update: will be called 60 times per second. This is where you modify the state of your scene, such as altering scores, removing dead invader sprites, or moving your ship around…

You’ll use update: to make your invaders move across and down the screen. Each time Sprite Kit invokes update:, it’s asking you “Did your scene change?”, “Did your scene change?”… It’s your job to answer that question — and you’ll write some code to do just that.

Insert the following code at the top of GameScene.m, just below the definition of the InvaderType enum:

typedef enum InvaderMovementDirection {
    InvaderMovementDirectionRight,
    InvaderMovementDirectionLeft,
    InvaderMovementDirectionDownThenRight,
    InvaderMovementDirectionDownThenLeft,
    InvaderMovementDirectionNone
} InvaderMovementDirection;

Invaders move in a fixed pattern: right, right, down, left, left, down, right, right, … so you’ll use the InvaderMovementDirection type to track the invaders’ progress through this pattern. For example, InvaderMovementDirectionRight means the invaders are in the right, right portion of their pattern.

Next, find the class extension in the same file and insert the following properties just below the existing property for contentCreated:

@property InvaderMovementDirection invaderMovementDirection;
@property NSTimeInterval timeOfLastMove;
@property NSTimeInterval timePerMove;

Add the following code to the very top of createContent:

//1
self.invaderMovementDirection = InvaderMovementDirectionRight;
//2
self.timePerMove = 1.0;
//3
self.timeOfLastMove = 0.0;

This one-time setup code initializes invader movement as follows:

  1. Invaders begin by moving to the right.
  2. Invaders take 1 second for each move. Each step left, right or down takes 1 second.
  3. Invaders haven’t moved yet, so set the time to zero.

Now, you’re ready to make the invaders move. Add the following code just below #pragma mark - Scene Update Helpers:

// This method will get invoked by update:
-(void)moveInvadersForUpdate:(NSTimeInterval)currentTime {
    //1
    if (currentTime - self.timeOfLastMove < self.timePerMove) return;
 
    //2
    [self enumerateChildNodesWithName:kInvaderName usingBlock:^(SKNode *node, BOOL *stop) {
        switch (self.invaderMovementDirection) {
            case InvaderMovementDirectionRight:
                node.position = CGPointMake(node.position.x + 10, node.position.y);
                break;
            case InvaderMovementDirectionLeft:
                node.position = CGPointMake(node.position.x - 10, node.position.y);
                break;
            case InvaderMovementDirectionDownThenLeft:
            case InvaderMovementDirectionDownThenRight:
                node.position = CGPointMake(node.position.x, node.position.y - 10);
                break;
            InvaderMovementDirectionNone:
            default:
                break;
        }
    }];
 
    //3
    self.timeOfLastMove = currentTime;
}

Here’s a breakdown of the code above, comment by comment:

  1. If it’s not yet time to move, then exit the method. moveInvadersForUpdate: is invoked 60 times per second, but you don’t want the invaders to move that often since the movement would be too fast for a normal person to see.
  2. Recall that your scene holds all of the invaders as child nodes; you added them to the scene using addChild: in setupInvaders identifying each invader by its name property. Invoking enumerateChildNodesWithName:usingBlock: only loops over the invaders because they’re named kInvaderName; this makes the loop skip your ship and the HUD. The guts of the block moves the invaders 10 pixels either right, left or down depending on the value of invaderMovementDirection.
  3. Record that you just moved the invaders, so that the next time this method is invoked (1/60th of a second from now), the invaders won’t move again till the set time period of one second has elapsed.

To make your invaders move, replace the existing update: method with the following:

-(void)update:(NSTimeInterval)currentTime {
    [self moveInvadersForUpdate:currentTime];
}

Build and run your app; you should see your invaders slowly walk their way across the screen. Keep watching, and you’ll eventually see the following screen:

Where did those invaders go?

Hmmm, what happened? Why did the invaders disappear? Maybe the invaders aren’t as menacing as you thought!

The invaders don’t yet know that they need to move down and change their direction once they hit the side of the playing field. Guess you’ll need to help those invaders find their way!

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:

Invaders moving right

Moving right

Invaders moving down then left

Moving down then left

Invaders moving left

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.

Controlling Ship Movements with Device Motion

You might be familiar with UIAccelerometer, which has been available since iOS 2.0 for detecting device tilt. However, UIAccelerometer was deprecated in iOS 5.0, so iOS 7 apps should use CMMotionManager, which is part of Apple’s CoreMotion framework.

The CoreMotion library has already been added to the starter project, so there’s no need for you to add it.

Your code can retrieve accelerometer data from CMMotionManager in two different ways:

Pushing accelerometer data to your code

In this scenario, you provide CMMotionManager with a block that it calls regularly with accelerometer data. This doesn’t fit well with your scene’s update: method that ticks at regular intervals of 1/60th of a second. You only want to sample accelerometer data during those ticks — and those ticks likely won’t line up with the moment that CMMotionManager decides to push data to your code.

Pulling accelerometer data from your code

In this scenario, you call CMMotionManager and ask it for data when you need it. Placing these calls inside your scene’s update: method aligns nicely with the ticks of your system. You’ll be sampling accelerometer data 60 times per second, so there’s no need to worry about lag.

Your app should only use a single instance of CMMotionManager to ensure you get the most reliable data. To that effect, add the following property to your class extension:

@property (strong) CMMotionManager* motionManager;

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

self.motionManager = [[CMMotionManager alloc] init];
[self.motionManager startAccelerometerUpdates];

This new code creates your motion manager and kicks off the production of accelerometer data. At this point, you can use the motion manager and its accelerometer data to control your ship’s movement.

Add the following method just below moveInvadersForUpdate::

-(void)processUserMotionForUpdate:(NSTimeInterval)currentTime {
    //1
    SKSpriteNode* ship = (SKSpriteNode*)[self childNodeWithName:kShipName];
    //2
    CMAccelerometerData* data = self.motionManager.accelerometerData;
    //3
    if (fabs(data.acceleration.x) > 0.2) {
      //4 How do you move the ship?
      NSLog(@"How do you move the ship: %@", ship);
    }
}

Dissecting this method, you’ll find the following:

  1. Get the ship from the scene so you can move it.
  2. Get the accelerometer data from the motion manager.
  3. If your device is oriented with the screen facing up and the home button at the bottom, then tilting the device to the right produces data.acceleration.x > 0, whereas tilting it to the left produces data.acceleration.x < 0. The check against 0.2 means that the device will be considered perfectly flat/no thrust (technically data.acceleration.x == 0) as long as it's close enough to zero (data.acceleration.x in the range [-0.2, 0.2]). There's nothing special about 0.2, it just seemed to work well for me. Little tricks like this will make your control system more reliable and less frustrating for users.
  4. Hmmm, how do you actually use data.acceleration.x to move the ship? You want small values to move the ship a little and large values to move the ship a lot. The answer is — physics, which you'll cover in the next section!

Translating Motion Controls into Movement via Physics

Sprite Kit has a powerful built-in physics system based on Box 2D that can simulate a wide range of physics like forces, translation, rotation, collisions, and contact detection. Each SKNode, and thus each SKScene and SKSpriteNode, has an SKPhysicsBody attached to it. This SKPhysicsBody represents the node in the physics simulation.

Add the following code right before the final return ship; line in makeShip:

//1
ship.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ship.frame.size];
//2
ship.physicsBody.dynamic = YES;
//3
ship.physicsBody.affectedByGravity = NO;
//4
ship.physicsBody.mass = 0.02;

Taking each comment in turn, you'll see the following:

  1. Create a rectangular physics body the same size as the ship.
  2. Make the shape dynamic; this makes it subject to things such as collisions and other outside forces.
  3. You don't want the ship to drop off the bottom of the screen, so you indicate that it's not affected by gravity.
  4. Give the ship an arbitrary mass so that its movement feels natural.

Now replace the NSLog statement in processUserMotionForUpdate: (right after comment //4) with the following:

[ship.physicsBody applyForce:CGVectorMake(40.0 * data.acceleration.x, 0)];

The new code applies a force to the ship's physics body in the same direction as data.acceleration.x. The number 40.0 is an arbitrary value to make the ship's motion feel natural.

Finally, add the following line to the top of update::

[self processUserMotionForUpdate:currentTime];

Your new processUserMotionForUpdate: now gets called 60 times per second as the scene updates.

Note: If you've been testing your code on simulator up till now, this would be the time to switch to your device. You won't be able to test the tilt code unless you are running the game on an actual device.

Build and run your game and try tilting your device left or right; your ship should respond to the accelerometer, as follows:

No_Player_Ship

What do you see? Your ship will fly off the side of the screen, lost in the deep, dark reaches of space. If you tilt hard and long enough in the opposite direction, you might get your ship to come flying back the other way. But at present, the controls are way too flaky and sensitive. You'll never kill any invaders like this!

An easy and reliable way to prevent things from escaping the bounds of your screen during a physics simulation is to build what's called an edge loop around the boundary of your screen. An edge loop is a physics body that has no volume or mass but can still collide with your ship. Think of it as an infinitely-thin wall around your scene.

Since your GameScene is a kind of SKNode, you can give it its own physics body to create the edge loop.

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

self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];

The new code adds the physics body to your scene.

Build and run your game once more and try tilting your device to move your ship, as below:

Player_Ship_In_Bounds

What do see? If you tilt your device far enough to one side, your ship will collide with the edge of the screen. It no longer flies off the edge of the screen. Problem solved!

Depending on the ship's momentum,you may also see the ship bouncing off the edge of the screen, instead of just stopping there. This is an added bonus that comes for free from Sprite Kit's physics engine — it's a property called restitution. Not only does it look cool, but it is what's known as an affordance since bouncing the ship back towards the center of the screen clearly communicates to the user that the edge of the screen is a boundary that cannot be crossed.

Where to Go From Here?

Here is the example project for the game up to this point.

So far, you've created invaders, your ship, and a Heads Up Display (HUD) and drawn them on-screen. You've also coded logic to make the invaders move automatically and to make your ship move as you tilt your device.

In part two of this tutorial, you'll add firing actions to your ship as well as the invaders, along with some collision detection so you'll know when you've hit the invaders — and vice versa! You'll also polish your game by adding both sound effects as well as realistic images to replace the colored rectangles that currently serve as placeholders for invaders and your ship.

In the meantime, if you have any questions or comments, please feel free to join in the discussions below!

Joel Shapiro

Joel develops iOS apps and Ruby on Rails backends as the founder and CTO of RepublicOfApps. He's also an aspiring game developer. You can follow him on Twitter as @RepublicOfApps. His latest game is Particle Parity — the classic memory matching game remade to match exciting particle effects.

User Comments

27 Comments

[ 1 , 2 ]
  • The download file does not work
    zardon
  • Sorry, I meant that the download hyperlink does not work, not the file.
    zardon
  • zardon wrote:Sorry, I meant that the download hyperlink does not work, not the file.


    Hi Zardon,

    I'm sorry you were having a problem with the download link. Can you try again? I just tried both links and they work for me.

    Please try again and let me know if you still have problems.
    jshapiro
  • Great tutorial. Love it. Don't mean to be a buzzkill but shouldn't this code be using typedef NS_ENUM() for enumerations as that is the Apple recommended way to do them safely in 32/64 environments? A lot of beginners look here and we want them to start out on the right foot.

    Good job over all. Thanks for the great tut!
    jonusx
  • jonusx wrote:Great tutorial. Love it. Don't mean to be a buzzkill but shouldn't this code be using typedef NS_ENUM() for enumerations as that is the Apple recommended way to do them safely in 32/64 environments? A lot of beginners look here and we want them to start out on the right foot.

    Good job over all. Thanks for the great tut!


    Not a buzzkill at all. You're right, using NS_ENUM is the better approach, but I wanted to keep things simple for the tutorial, so I just used a straight typedef. I do use NS_ENUM in my own apps. Thanks for pointing this out.

    Cheers!
    jshapiro
  • Great work on the tutorial.

    Not saying Im right or anything, but shouldn't the GameScene have a init method to initialize all variables instead of putting them all in createContent?

    Code: Select all

    - (id)initWithSize:(CGSize)size {
        self = [super initWithSize:size];
        if (self) {
            _invaderMovementDirection = InvaderMovementDirectionRight;
            _timeOfLastMove = 0.0f;
            _timePerMove = 1.0f;
            _motionManager = [[CMMotionManager alloc] init];
        }
        return self;
    }
    xr1337
  • In the old school game there we barriers. These would get removed bit by bit with alien bullet hits... can you do that in sprite kit ? Some how remove pixels bit by bit ??
    smick
  • smick wrote:In the old school game there we barriers. These would get removed bit by bit with alien bullet hits... can you do that in sprite kit ? Some how remove pixels bit by bit ??


    Check out my post at http://www.raywenderlich.com/forums/viewtopic.php?f=20&t=9088&start=10#p49123

    The shields are just rectangular blocks made up of individual bricks, where each brick is a sprite. As the bullets (from both invaders and the ship) hit a brick, I remove the sprite.

    Have fun!
    Richard Caseyrcasey
  • Hi Joel
    This tutorial looks fantastic and curiously enough, just what I am looking for. However I am planning on using iphone 4 as dev environment. Due to many complaints of problems with ios7 with iphone 4 I have not yet upgraded. Do you know anything about this, and would you recommend upgrading my iphone 4 despite these cited issues? It seems that ios 7 has many desirable features for devs.
    Thanks in advance
    Tk
    tinkerking
  • Greate tutorial! Thank you very much!
    ricolwang
  • tinkerking wrote:Hi Joel
    This tutorial looks fantastic and curiously enough, just what I am looking for. However I am planning on using iphone 4 as dev environment. Due to many complaints of problems with ios7 with iphone 4 I have not yet upgraded. Do you know anything about this, and would you recommend upgrading my iphone 4 despite these cited issues? It seems that ios 7 has many desirable features for devs.
    Thanks in advance
    Tk


    Hi TK,

    I use an old iPhone 4 running iOS 7 for testing and I find it works well enough. It's not a quick as my iPhone 5 running iOS 7, but I'd call it smooth enough for every day use, especially with iOS 7.1. But that's just my opinion - your experience may vary ...

    Good luck!

    Joel
    jshapiro
  • I keep getting a gray screen when I run the app in the Simulator. Has anyone found a workaround for this? Thanks!
    steve202
[ 1 , 2 ]

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Our Books

Our Team

Tutorial Team

  • Dani Arnaout

... 50 total!

Update Team

... 15 total!

Editorial Team

  • John Clem

... 23 total!

Code Team

  • Orta Therox

... 3 total!

Translation Team

  • Lin Ma

... 33 total!

Subject Matter Experts

... 4 total!