How to Make a Game Like Mega Jump With Sprite Kit: Part 2/2

In Part Two of this tutorial series, you’ll finish developing your Uber Jump game by adding accelerometer-driven controls, a scoring system and more. By Toby Stephens.

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

Parallaxalization

No, that’s not a word! ;]

To give your game the parallax effect, you’re going to move the background, midground and foreground nodes at different rates as the player moves up and down the scene. Sprite Kit calls update: on your scene every frame, so that’s the place to implement this logic to produce smooth animation.

Open MyScene.m and add the following method:

- (void) update:(CFTimeInterval)currentTime {
  // Calculate player y offset
  if (_player.position.y > 200.0f) {
    _backgroundNode.position = CGPointMake(0.0f, -((_player.position.y - 200.0f)/10));
    _midgroundNode.position = CGPointMake(0.0f, -((_player.position.y - 200.0f)/4));
    _foregroundNode.position = CGPointMake(0.0f, -(_player.position.y - 200.0f));
  }
}

You check to make sure the player node has moved up the screen at least 200 points, because otherwise you don’t want to move the background. If so, you move the three nodes down at different speeds to produce a parallax effect:

  • You move the foreground node at the same rate as the player node, effectively keeping the player from moving any higher on the screen.
  • You move the midground node at 25% of the player node’s speed so that it appears to be farther away from the viewer.
  • You move the background node at 10% of the player node’s speed so that it appears even farther away.

Build and run, and tap to start the game. You’ll see the layers all move with the player, and the different speeds of the background and midground nodes will produce a very pleasing parallax effect.

20-Parallax

Great work! But don’t rest on your laurels yet. To get any higher in this world, you need to get your tilt on.

Moving With the Accelerometer

It’s now time to consider the accelerometer. You’ve got movement along the vertical axis working well, but what about movement along the horizontal axis? Just like in Mega Jump, the player will steer their Uber Jumper using the accelerometer.

Note: To test accelerometer, you need to run your game on a device. The iPhone Simulator does not simulate accelerometer inputs.

The accelerometer inputs are part of the Core Motion library, so at the top of MyScene.m, add the following import:

@import CoreMotion;

Then add the following lines to the MyScene class extension:

// Motion manager for accelerometer
CMMotionManager *_motionManager;

// Acceleration value from accelerometer
CGFloat _xAcceleration;

You’re going to use _motionManager to access the device’s accelerometer data, and you’ll store the most recently calculated acceleration value in _xAcceleration, which you’ll use later when you set the player node’s velocity along the x-axis.

To instantiate your CMMotionManager, add the following code to initWithSize:, just after the line that adds _tapToStartNode to the HUD:

// CoreMotion
_motionManager = [[CMMotionManager alloc] init];
// 1
_motionManager.accelerometerUpdateInterval = 0.2;
// 2
[_motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue currentQueue]
  withHandler:^(CMAccelerometerData  *accelerometerData, NSError *error) {
    // 3
    CMAcceleration acceleration = accelerometerData.acceleration;
    // 4
    _xAcceleration = (acceleration.x * 0.75) + (_xAcceleration * 0.25);
  }];

There’s a lot going on here, so take a deeper dive:

  1. accelerometerUpdateInterval defines the number of seconds between updates from the accelerometer. A value of 0.2 produces a smooth update rate for accelerometer changes.
  2. You enable the accelerometer and provide a block of code to execute upon every accelerometer update.
  3. Inside the block, you get the acceleration details from the latest accelerometer data passed into the block.
  4. Here you calculate the player node’s x-axis acceleration. You could use the x-value directly from the accelerometer data, but you’ll get much smoother movement using a value derived from three quarters of the accelerometer’s x-axis acceleration (say that three times fast!) and one quarter of the current x-axis acceleration.

Now that you have an x-axis acceleration value, you need to use that value to set the player node’s velocity along the x-axis.

As you are directly manipulating the velocity of the player node, it’s important to let Sprite Kit handle the physics first. Sprite Kit provides a method for that very purpose called didSimulatePhysics. You should call it as soon as Sprite Kit has dealt with the game’s physics for each frame.

Add the following method to MyScene.m:

- (void) didSimulatePhysics
{
  // 1
  // Set velocity based on x-axis acceleration
  _player.physicsBody.velocity = CGVectorMake(_xAcceleration * 400.0f, _player.physicsBody.velocity.dy);

  // 2
  // Check x bounds
  if (_player.position.x < -20.0f) {
    _player.position = CGPointMake(340.0f, _player.position.y);
  } else if (_player.position.x > 340.0f) {
    _player.position = CGPointMake(-20.0f, _player.position.y);
  }
  return;
}

A couple of things are happening here:

  1. You change the x-axis portion of the player node’s velocity using the _xAcceleration value. You multiply it by 400.0 because the accelerometer scale does not match the physics world’s scale and so increasing it produces a more satisfying result. You leave the velocity’s y-axis value alone because the accelerometer has no effect on it.
  2. In Mega Jump, when the player leaves the screen from the left or right, they come back onscreen from the other side. You replicate that behavior here by checking the bounds of the screen and leaving a 20-point border at the edges.

Build and run on your device, and use the accelerometer to steer the player sprite as high as you can!

21-AccelerometerRun

The Scoring System

Your complete Uber Jump will have three pieces of information pertinent to the player:

  • The Current Score. Each game, the score will start at zero. The higher the player gets, the more points you’ll award toward the score. You’ll also add points for each star the player collects.
  • The High Score. Every run through the game will result in a final score. Uber Jump will save the highest of these in the app’s user defaults so that the player always knows the score to beat.
  • The Stars. While the current score will reset at the beginning of each game, the player’s stars will accumulate from game to game. In a future version of Uber Jump, you may decide to make stars the in-game currency with which users can buy upgrades and boosts. You won’t be doing that as part of this tutorial, but you’ll have the currency in case you’d like to do it on your own.

You’re going to store the current score, the high score and the number of the collected stars in a singleton class called GameState. This class will also be responsible for saving the high score and number of stars to the device so that the values persist between game launches.

Create a new Objective-C class called GameState and make it a subclass of NSObject.

22-NewClassGameState

Add the following properties and method to GameState.h:

@property (nonatomic, assign) int score;
@property (nonatomic, assign) int highScore;
@property (nonatomic, assign) int stars;

+ (instancetype)sharedInstance;

These three properties will provide access to the current score, the high score and the star count. The static method sharedInstance will provide access to the singleton instance of GameState.

Now add the following to GameState.m:

+ (instancetype)sharedInstance
{
  static dispatch_once_t pred = 0;
  static GameState *_sharedInstance = nil;
  
  dispatch_once( &pred, ^{
    _sharedInstance = [[super alloc] init];
  });
  return _sharedInstance;
}

This is the standard way to create a singleton object in Objective-C. It ensures that the GameState referenced by _sharedInstance is only initialized once no matter how many times you call this method. Now you can use the same object from anywhere in your code to access the current game state.

You need to provide an initialization method for GameState that sets the current score to zero and loads any existing high score and star count from the user defaults.

Add the following init method to GameState.m:

- (id) init
{
  if (self = [super init]) {
    // Init
    _score = 0;
    _highScore = 0;
    _stars = 0;

    // Load game state
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    id highScore = [defaults objectForKey:@"highScore"];
    if (highScore) {
      _highScore = [highScore intValue];
    }
    id stars = [defaults objectForKey:@"stars"];
    if (stars) {
      _stars = [stars intValue];
    }
  }
  return self;
}

NSUserDefaults is a simple way to persist small bits of data on the device. It’s intended for user preferences, but in this example it works well to store the high score and star count. In a real app, do not use NSUserDefaults as someone could easily tamper with the data stored there.

Note:There is a complete tutorial called “How to store your game’s data” coming out the week after this one. It will introduce you to using the Keychain, iCloud and more for storing your high scores and other game data.

To store these values, you need a method in GameState. Add the following method declaration to GameState.h:

- (void) saveState;

Now add the implementation to GameState.m:

- (void) saveState
{
  // Update highScore if the current score is greater
  _highScore = MAX(_score, _highScore);

  // Store in user defaults
  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  [defaults setObject:[NSNumber numberWithInt:_highScore] forKey:@"highScore"];
  [defaults setObject:[NSNumber numberWithInt:_stars] forKey:@"stars"];
  [[NSUserDefaults standardUserDefaults] synchronize];
}

You now have a GameState class that synchronizes with storage on the device. Since the app will use GameState throughout, you can add the GameState.h import to the project’s precompile header file.

Locate and open UberJump-Prefix.pch in the Project Navigator. Add the following import after the imports for the UIKit and Foundation header files:

#import "GameState.h"

This gives each of your classes access to GameState without having to import its header file directly.

What good is a score if nobody knows what it is? You’ve got to show me the money!

Toby Stephens

Contributors

Toby Stephens

Author

Over 300 content creators. Join our team.