Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all articles
Browse latest Browse all 4384

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

$
0
0
Create a game like Mega Jump using Sprite Kit

Make a game like Mega Jump using Sprite Kit!

Welcome to the second part of the tutorial series that walks you through using Sprite Kit to create a game like Mega Jump.

In the first part of the tutorial, you created a new Sprite Kit game called “Uber Jump.” You added graphics, a player sprite and some gameplay elements.

In this second part, you’ll use that firm foundation to build an entire level for Uber Jump, including a scoring system. You’ll also add accelerometer support so that your Uber Jumper can move from side to side as well as up and down. When you’re done, you’ll have a completely playable game that you could expand in many different ways.

As with Part One, be sure you are familiar with the basics of Sprite Kit before continuing.

Your level awaits—so let’s jump to it!

Getting Started

If you don’t have it already, grab a copy of the complete project from Part One.

Your level will contain many stars and platforms. Rather than arrange them manually, download this level configuration file. Unzip it and drag Level01.plist into your Xcode project. Make sure that Copy items into destination group’s folder (if needed) is checked and that your UberJump target is selected.

Open Level01.plist and examine its contents. At the root, it has three elements:

  • EndY specifies the height the player must reach to finish the level.
  • Stars defines the positions of all the stars in the level.
  • Platforms defines the positions of all the platforms in the level.

The Stars and Platforms elements each contain two sub-elements:

  • Patterns contains a number of reusable patterns of stars or platforms.
  • Positions specifies where to place the patterns of stars or platforms throughout the level.

jm_level_plist

To better understand the file format, take a look at Stars/Positions/Item 0. This contains three elements telling the game to place stars in a cross pattern positioned at (160, 240).

jm_level_plist1

Now look at Patterns/Cross and you’ll see this pattern is made up of five items, including (x, y) coordinates relative to the position given in Stars/Positions and the type of star, where NORMAL=0 or SPECIAL=1.

jm_level_plist2

This is simply a convenient way of reusing patterns of stars and platforms without having to code the position of every individual object.

rrr

Loading the Level Data

To add support for loading the level from Level01.plist, open MyScene.m and add the following instance variable to the class extension in MyScene.m:

// Height at which level ends
int _endLevelY;

_endLevelY will store the height, or y-value, that the player must reach to finish the level.

Insert the following code into initWithSize:, just before the lines that instantiate and add platform:

// Load the level
NSString *levelPlist = [[NSBundle mainBundle] pathForResource: @"Level01" ofType: @"plist"];
NSDictionary *levelData = [NSDictionary dictionaryWithContentsOfFile:levelPlist];
 
// Height at which the player ends the level
_endLevelY = [levelData[@"EndY"] intValue];

This loads the data from the property list into a dictionary named levelData and stores the property list’s EndY value in _endLevelY.

Now for the stars and platforms. Begin with the platforms. In initWithSize:, replace the following lines:

// Add a platform
PlatformNode *platform = [self createPlatformAtPosition:CGPointMake(160, 320) ofType:PLATFORM_NORMAL];
[_foregroundNode addChild:platform];

With this code:

// Add the platforms
NSDictionary *platforms = levelData[@"Platforms"];
NSDictionary *platformPatterns = platforms[@"Patterns"];
NSArray *platformPositions = platforms[@"Positions"];
for (NSDictionary *platformPosition in platformPositions) {
  CGFloat patternX = [platformPosition[@"x"] floatValue];
  CGFloat patternY = [platformPosition[@"y"] floatValue];
  NSString *pattern = platformPosition[@"pattern"];
 
  // Look up the pattern
  NSArray *platformPattern = platformPatterns[pattern];
  for (NSDictionary *platformPoint in platformPattern) {
    CGFloat x = [platformPoint[@"x"] floatValue];
    CGFloat y = [platformPoint[@"y"] floatValue];
    PlatformType type = [platformPoint[@"type"] intValue];
 
    PlatformNode *platformNode = [self createPlatformAtPosition:CGPointMake(x + patternX, y + patternY)
                                                         ofType:type];
    [_foregroundNode addChild:platformNode];
  }
}

There’s a lot going on here, but it’s simple stuff. You load the Platforms dictionary from levelData and then loop through its Positions array. For each item in the array, you load the relevant pattern and instantiate PlatformNodes of the correct type at the specified (x, y) positions. You add all the platform nodes to the foreground node, where all the game objects belong.

Build and run. You’ll see a set of three platforms aligned in the scene, which is the “Triple” pattern described in Level01.plist.

16-Level01Platforms

Now do the same for the stars. Inside MyScene.m, replace the following line in initWithSize::

// Add a star
StarNode *star = [self createStarAtPosition:CGPointMake(160, 220) ofType:STAR_SPECIAL];
[_foregroundNode addChild:star];

With this code:

// Add the stars
NSDictionary *stars = levelData[@"Stars"];
NSDictionary *starPatterns = stars[@"Patterns"];
NSArray *starPositions = stars[@"Positions"];
for (NSDictionary *starPosition in starPositions) {
  CGFloat patternX = [starPosition[@"x"] floatValue];
  CGFloat patternY = [starPosition[@"y"] floatValue];
  NSString *pattern = starPosition[@"pattern"];
 
  // Look up the pattern
  NSArray *starPattern = starPatterns[pattern];
  for (NSDictionary *starPoint in starPattern) {
    CGFloat x = [starPoint[@"x"] floatValue];
    CGFloat y = [starPoint[@"y"] floatValue];
    StarType type = [starPoint[@"type"] intValue];
 
    StarNode *starNode = [self createStarAtPosition:CGPointMake(x + patternX, y + patternY) ofType:type];
    [_foregroundNode addChild:starNode];
  }
}

This is exactly what you did to create the platforms, but this time you create stars for the items in the Stars dictionary.

Build and run. This is starting to look like a real game!

17-Level01Stars

The Midground Layer

Graphically, there’s just one more thing to add to give the game a greater illusion of depth, and that’s the midground layer. This is the node that’s going to contain decorative graphics to bring the game to life.

Add the following method to MyScene.m:

- (SKNode *)createMidgroundNode
{
  // Create the node
  SKNode *midgroundNode = [SKNode node];
 
  // 1
  // Add some branches to the midground
  for (int i=0; i<10; i++) {
    NSString *spriteName;
    // 2
    int r = arc4random() % 2;
    if (r > 0) {
      spriteName = @"BranchRight";
    } else {
      spriteName = @"BranchLeft";
    }
    // 3
    SKSpriteNode *branchNode = [SKSpriteNode spriteNodeWithImageNamed:spriteName];
    branchNode.position = CGPointMake(160.0f, 500.0f * i);
    [midgroundNode addChild:branchNode];
  }
 
  // Return the completed background node
  return midgroundNode;	
}

Take a closer look at this code:

  1. You add ten branches to midgroundNode, spaced evenly throughout the level.
  2. There are two different branch images, one showing branches coming in from the left of the screen and the other from the right. Here you grab one randomly.
  3. You space the branches at 500-point intervals on the y-axis of the midground node.

Now add the midground node to your scene by inserting the following code into initWithSize:, immediately after the line that adds the background node:

// Midground
_midgroundNode = [self createMidgroundNode];
[self addChild:_midgroundNode];

Build and run. Look! It’s a branch (of sorts) and maybe some pink butterflies!

18-MidgroundNode

Note: The pink butterflies will only appear if the randomly-chosen branch is the one coming from the right side of the screen. The other branch image does not include the butterflies.

Tap to start the game and you will see the player sprite shoot up the screen. However, even as the Uber Jumper ascends, the game world remains still.

19-MidgroundNodeRun

The background, midground and foreground layers should all move with the player node to keep the player sprite in the center of the screen. You’re going to sort that out next.

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!

Building the HUD

Before you start awarding points, you’re going to build a simple HUD so that the player can see their score and star count.

Your HUD will show the total number of collected stars on the top-left of the scene and the current score on the top-right of the scene. For this, you need to create two SKLabelNodes.

Add the following variable declarations to the class extension in MyScene.m:

// Labels for score and stars
SKLabelNode *_lblScore;
SKLabelNode *_lblStars;

To build the HUD, add the following code to initWithSize: in MyScene.m, just before the line that initializes _motionManager:

// Build the HUD
 
// Stars
// 1
SKSpriteNode *star = [SKSpriteNode spriteNodeWithImageNamed:@"Star"];
star.position = CGPointMake(25, self.size.height-30);
[_hudNode addChild:star];
// 2
_lblStars = [SKLabelNode labelNodeWithFontNamed:@"ChalkboardSE-Bold"];
_lblStars.fontSize = 30;
_lblStars.fontColor = [SKColor whiteColor];
_lblStars.position = CGPointMake(50, self.size.height-40);
_lblStars.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeLeft;
// 3
[_lblStars setText:[NSString stringWithFormat:@"X %d", [GameState sharedInstance].stars]];
[_hudNode addChild:_lblStars];
 
// Score
// 4
_lblScore = [SKLabelNode labelNodeWithFontNamed:@"ChalkboardSE-Bold"];
_lblScore.fontSize = 30;
_lblScore.fontColor = [SKColor whiteColor];
_lblScore.position = CGPointMake(self.size.width-20, self.size.height-40);
_lblScore.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeRight;
// 5
[_lblScore setText:@"0"];
[_hudNode addChild:_lblScore];

Take a closer look at this section of code:

  1. First you add a star graphic in the top-left corner of the scene to tell the player that the following number is the collected star count.
  2. Next to the star, you place a left-aligned SKLabelNode.
  3. You initialize the label with the number of stars from GameState.
  4. You add a right-aligned SKLabelNode in the top-right corner of the scene.
  5. You initialize that label to zero, as there is no score currently.

Build and run. You’ll see the two labels at the top of the screen.

23-HUD

The last layer of your game is complete! Now let’s have some points already!

Awarding Points

It’s finally time to award points for the player’s hard work. There are two ways to score points in Uber Jump: climbing up the scene and collecting stars.

To award points for collecting stars, simply add the following code to the bottom of collisionWithPlayer: in StarNode.m, just before the return statement:

// Award score
[GameState sharedInstance].score += (_starType == STAR_NORMAL ? 20 : 100);

That’s it! You add 20 points to the score for a normal star and 100 points for the special type.

To show this updated score, go back to didBeginContact: in MyScene.m. Recall from Part One of this tutorial that this method sets a flag named updateHUD to YES when it determines that the values displayed in the HUD need to change.

Add the following two lines of code to didBeginContact: inside the if(updateHUD) {} condition curly braces. There should be a comment there that reads TODO: Update HUD in Part 2:

[_lblStars setText:[NSString stringWithFormat:@"X %d", [GameState sharedInstance].stars]];
[_lblScore setText:[NSString stringWithFormat:@"%d", [GameState sharedInstance].score]];

Build and run, and tap to start the game. As you collect stars, watch your score increase.

24-HUDPointsFromStars

To work out when to award points for traveling up the screen, you need to store the highest point along the y-axis that the player has reached during this play-through. You’ll use this data to increase the score only when the player reaches a new high point, rather than award points constantly as the player bobs up and down the screen.

Add the following instance variable to the class extension in MyScene.m:

// Max y reached by player
int _maxPlayerY;

The player node initially starts at a y-coordinate of 80.0, so you need to initialize _maxPlayerY to 80 if you don’t want to give the player 80 points just for starting the game. ;]

Add the following line to initWithSize: in MyScene.m, just after the line that sets the background color:

// Reset
_maxPlayerY = 80;

To increase the score when the player travels up the screen, go to MyScene.m. At the top of update:, add the following lines:

// New max height ?
// 1
if ((int)_player.position.y > _maxPlayerY) {
  // 2
  [GameState sharedInstance].score += (int)_player.position.y - _maxPlayerY;
  // 3
  _maxPlayerY = (int)_player.position.y;
  // 4
  [_lblScore setText:[NSString stringWithFormat:@"%d", [GameState sharedInstance].score]];
}

This deserves closer inspection:

  1. First, you check whether the player node has travelled higher than it has yet travelled in this play-through.
  2. If so, you add to the score the difference between the player node’s current y-coordinate and the max y-value.
  3. You set the new max y-value.
  4. Finally, you update the score label with the new score.

Build and run, and tap to start. As you play, your score will go up as you move higher through the level.

25-HUDPointsFromY

Now consider the star count. You need to increment it every time the player node collides with a star, so open StarNode.m and add the following code to collisionWithPlayer:, just after the line that awards the points:

// Award stars
[GameState sharedInstance].stars += (_starType == STAR_NORMAL ? 1 : 5);

That’s all that needs doing! Build and run, and tap to start. Watch the star count increase as you collect them.

26-HUDStars

As you play, you may notice that when you fall, all the game objects you passed are still in the game. “Hey!” you are surely thinking “I spent a considerable amount of time with Mega Jump, and I am sure that’s not how it was in that game!” Yes, but that’s easy to fix.

Recall that you added a method named checkForNodeRemoval: to GameObjectNode that checks whether or not to remove a node. It’s now time to call that method every frame.

Add the following code to update: in MyScene.m, just before the line that checks if the player node’s position is greater than 200:

// Remove game objects that have passed by
[_foregroundNode enumerateChildNodesWithName:@"NODE_PLATFORM" usingBlock:^(SKNode *node, BOOL *stop) {
  [((PlatformNode *)node) checkNodeRemoval:_player.position.y];
}];
[_foregroundNode enumerateChildNodesWithName:@"NODE_STAR" usingBlock:^(SKNode *node, BOOL *stop) {
  [((StarNode *)node) checkNodeRemoval:_player.position.y];
}];

Here you enumerate through the platforms in the foreground node and call checkNodeRemoval: for each one. You then do the same for each of the stars.

Build and run, and tap to start. Now when you fall, it’s nothing but empty sky!

27-RemoveOldGameNodes

Game Over!

At the end of the game, when the player falls off the bottom of the scene or climbs to the top of the level, you want to show the final score and the current high score. You’ll do it by transitioning to an end game scene.

Create a new Objective-C class called EndGameScene and make it a subclass of SKScene.

28-NewClassEndGameScene

EndGameScene will be a simple screen that shows the player’s score, their star count and the current high score. You add all the nodes in initWithSize:, so open EndGameScene.m and add the following method:

- (id) initWithSize:(CGSize)size
{
  if (self = [super initWithSize:size]) {
    // Stars
    SKSpriteNode *star = [SKSpriteNode spriteNodeWithImageNamed:@"Star"];
    star.position = CGPointMake(25, self.size.height-30);
    [self addChild:star];
    SKLabelNode *lblStars = [SKLabelNode labelNodeWithFontNamed:@"ChalkboardSE-Bold"];
    lblStars.fontSize = 30;
    lblStars.fontColor = [SKColor whiteColor];
    lblStars.position = CGPointMake(50, self.size.height-40);
    lblStars.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeLeft;
    [lblStars setText:[NSString stringWithFormat:@"X %d", [GameState sharedInstance].stars]];
    [self addChild:lblStars];
 
    // Score
    SKLabelNode *lblScore = [SKLabelNode labelNodeWithFontNamed:@"ChalkboardSE-Bold"];
    lblScore.fontSize = 60;
    lblScore.fontColor = [SKColor whiteColor];
    lblScore.position = CGPointMake(160, 300);
    lblScore.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeCenter;
    [lblScore setText:[NSString stringWithFormat:@"%d", [GameState sharedInstance].score]];
    [self addChild:lblScore];
 
    // High Score
    SKLabelNode *lblHighScore = [SKLabelNode labelNodeWithFontNamed:@"ChalkboardSE-Bold"];
    lblHighScore.fontSize = 30;
    lblHighScore.fontColor = [SKColor cyanColor];
    lblHighScore.position = CGPointMake(160, 150);
    lblHighScore.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeCenter;
    [lblHighScore setText:[NSString stringWithFormat:@"High Score: %d", [GameState sharedInstance].highScore]];
    [self addChild:lblHighScore];
 
    // Try again
    SKLabelNode *lblTryAgain = [SKLabelNode labelNodeWithFontNamed:@"ChalkboardSE-Bold"];
    lblTryAgain.fontSize = 30;
    lblTryAgain.fontColor = [SKColor whiteColor];
    lblTryAgain.position = CGPointMake(160, 50);
    lblTryAgain.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeCenter;
    [lblTryAgain setText:@"Tap To Try Again"];
    [self addChild:lblTryAgain];
  }
  return self;
}

That’s a lot of code, but by now, after all you’ve done in this tutorial, you’ve got pwnage.

In brief, you create three labels: one each to show the star count, the final score and the high score. You populate these labels with values from the GameState singleton. You also add a label explaining to the player that they can tap the screen to play again.

To track whether or not the game is over, add the following BOOL to the class extension in MyScene.m:

// Game over dude !
BOOL _gameOver;

At the end of the game, the GameState singleton needs to save the current state and transition to the new scene. In MyScene.m, add the following import at the top:

#import "EndGameScene.h"

Now, add the following method to MyScene.m:

- (void) endGame
{
  // 1
  _gameOver = YES;
 
  // 2
  // Save stars and high score
  [[GameState sharedInstance] saveState];
 
  // 3
  SKScene *endGameScene = [[EndGameScene alloc] initWithSize:self.size];
  SKTransition *reveal = [SKTransition fadeWithDuration:0.5];
  [self.view presentScene:endGameScene transition:reveal];
}

Look at this method in detail:

  1. First you set _gameOver to YES.
  2. Then you instruct the GameState singleton to save the game state to the app’s user defaults.
  3. Finally, you instantiate an EndGameScene and transition to it by fading over a period of 0.5 seconds.

The game needs to call endGame when the player node either falls off the bottom of the screen or reaches the maximum height for the level. You’ll test for both of these triggers in update: in the main scene.

Open MyScene.m and add the following code to the end of update::

// 1
// Check if we've finished the level
if (_player.position.y > _endLevelY) {
  [self endGame];
}
 
// 2
// Check if we've fallen too far
if (_player.position.y < (_maxPlayerY - 400)) {
  [self endGame];
}

Take a look at these checks:

  1. Remember, you loaded _endLevelY from the level’s property list—it’s the y-value at which the player has finished the level.
  2. If the player node falls by more than 400 points below the max height they’ve reached, then it’s game over.

Before you can run the game to see your end game scene, you need to make sure the game doesn’t try to call endGame more than once, which might happen if update: runs again for another frame.

Add the following line to the start of update: in MyScene.m:

if (_gameOver) return;

Now, when the game calls update:, the method checks to see if the game is already over before progressing.

Build and run. Tap to start and then play the game, allowing the Uber Jumper to fall at some point. The game will transition to the end game scene.

UberJump-EndGameScene

Restarting

To restart the game from the end game scene, you need to modify EndGameScene so it transitions back to the main game scene upon a touch event.

Open EndGameScene.m and add the following import at the top:

#import "MyScene.h"

Now add the following method to handle touch events:

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
  // Transition back to the Game
  SKScene *myScene = [[MyScene alloc] initWithSize:self.size];
  SKTransition *reveal = [SKTransition fadeWithDuration:0.5];
  [self.view presentScene:myScene transition:reveal];
}

This code simply transitions to a new MyScene in much the same way you transition to this scene when the app first starts.

Build and run, and tap to start. Play the game and fall to reveal the end game scene. Tap the screen and watch the game restart.

uj_bg_noresetscore

Whoops, you forgot to reset the score! Remedy that now by stopping the app and going to MyScene.m. In initWithSize:, where you reset _maxPlayerY, add the following code:

[GameState sharedInstance].score = 0;
_gameOver = NO;

This resets the score in the GameState singleton and also resets the _gameOver flag.

Build and run again. Tap to start and play the game. When you get to the end game scene, whether from top or bottom, tap to try again. This time, the score resets to zero.

uj_bg_resetscore

Now go and beat that high score! ;]

Where to Go From Here?

Congratulations, you’ve made a game like Mega Jump!

Here is a sample project with all of the code from this tutorial series.

In the 2 parts of this series you’ve covered the whole process of creating a physics based game. You learned much about how to setup collisions and how to build your game logic around detecting contacts in the game.

But you can do much more for your Uber Jump game! Open up the plist level data and think of new levels. What new shapes could you implement? You can just copy the plist source code and create any number of levels by adjusting what’s inside the level file.

Play some Mega Jump and get inspiration! Can you implement some better game feat compared to the original game?

And last but not least if you want to learn more about Sprite Kit, check out the iOS Games by Tutorials book, where you’ll learn how to make five complete games—from zombie action to car racing to shootouts in space!

In the meantime, if you have any questions or comments, please join the forum discussion below.

How to Make a Game Like Mega Jump With Sprite Kit: Part 2/2 is a post from: Ray Wenderlich

The post How to Make a Game Like Mega Jump With Sprite Kit: Part 2/2 appeared first on Ray Wenderlich.


Viewing all articles
Browse latest Browse all 4384

Trending Articles