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

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.

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.