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 .
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How To Make a Game Like Space Invaders with Sprite Kit Tutorial: Part 1
40 mins
- Getting Started
- The Role of GameScene
- Creating the Evil Invaders from Space
- Create Your Valiant Ship
- Adding the Heads Up Display (HUD)
- Adding Motion to the Invaders
- Controlling the Invaders' Direction
- Adding Motion to your Ship
- Controlling Ship Movements with Device Motion
- Translating Motion Controls into Movement via Physics
- Where to Go From Here?
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:
- Get the ship from the scene so you can move it.
- Get the accelerometer data from the motion manager.
- 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 producesdata.acceleration.x < 0
. The check against 0.2 means that the device will be considered perfectly flat/no thrust (technicallydata.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. - 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:
- Create a rectangular physics body the same size as the ship.
- Make the shape dynamic; this makes it subject to things such as collisions and other outside forces.
- You don't want the ship to drop off the bottom of the screen, so you indicate that it's not affected by gravity.
- 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:
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:
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.