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

In this two-part tutorial series, you’ll learn how to create a game like Mega Jump using the latest and greatest game framework for iOS: Sprite Kit. By Toby Stephens.

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

Game Objects: Reach for the Stars!

To give your player something to do—as well as some upward momentum—it’s “high” time for you to add stars to your game. Stars have a key role in Uber Jump: They are what the player needs to grab to move higher into the level.

Initially, you will add one star to the game and implement it fully. Then in Part Two of this tutorial, you’ll complete the level.

In Uber Jump, as in Mega Jump, when the player sprite goes a certain distance higher than a star, platform or any other game object, the game will remove that object from the scene. Since star and platform nodes share this functionality, it makes sense to create a subclass of SKNode for all game objects.

Create a new Objective-C class called GameObjectNode and make it a subclass of SKNode.

08-NewClassGameObjectNode

GameObjectNode will provide the following functionality:

  • It will remove the game object from the scene if the player node has passed it by more than a set distance.
  • It will handle collisions between the player node and the object. This method will return a BOOL that informs the scene whether the collision with the game object has resulted in a need to update the HUD—for example, if the player has scored points.

Add the following method declarations to GameObjectNode.h:

// Called when a player physics body collides with the game object's physics body
- (BOOL) collisionWithPlayer:(SKNode *)player;

// Called every frame to see if the game object should be removed from the scene
- (void) checkNodeRemoval:(CGFloat)playerY;

You'll call collisionWithPlayer: whenever the player node collides with this object, and you'll call checkNodeRemoval: every frame to give the node a chance to remove itself.

Add the following definitions for these methods to GameObjectNode.m:

- (BOOL) collisionWithPlayer:(SKNode *)player
{
  return NO;
}

- (void) checkNodeRemoval:(CGFloat)playerY
{
  if (playerY > self.position.y + 300.0f) {
    [self removeFromParent];
  }
}

In the GameObjectNode class, collisionWithPlayer: is simply a stub. You will define the full method in each of your game object subclasses.

checkNodeRemoval: checks to see if the player node has traveled more than 300 points beyond this node. If so, then the method removes the node from its parent node and thus, removes it from the scene.

The Star Class

Now that you have a base class for the interactive game nodes, you can create a class for your stars. Create a new Objective-C class called StarNode and make it a subclass of GameObjectNode.

09-NewClassStarNode

In StarNode.m, add the following implementation of collisionWithPlayer: for the star:

- (BOOL) collisionWithPlayer:(SKNode *)player
{
  // Boost the player up
  player.physicsBody.velocity = CGVectorMake(player.physicsBody.velocity.dx, 400.0f);

  // Remove this star
  [self removeFromParent];

  // The HUD needs updating to show the new stars and score
  return YES;
}

Collision with a star boosts the player node up the y-axis. You may be thinking, “Why am I not using a force or impulse in the physics engine to do this?”

If you were to apply the star boost as a force or impulse, it wouldn’t always have the same effect. For example, if the player node were moving down the screen when it collided with the star, then the force would have a much weaker effect on the player than if the player were already moving up the screen.

The following diagram shows a very simplified visualization of this:

10-ForcesAndBounce

A solution to this problem is to change the player node's velocity directly. The player node’s velocity is obviously made up of an x-axis speed and a y-axis speed.

The x-axis speed needs to stay the same, since it is only affected by the accelerometer, which you’ll implement later. In the method above, you set the y-axis velocity to 400 on collision—a fixed amount so that the collision has the same effect no matter what the player node is doing when it collides with the star.

Now add your new star class to the scene. Open MyScene.m and import your StarNode class header:

#import "StarNode.h"

Now add the following method to MyScene:

- (StarNode *) createStarAtPosition:(CGPoint)position
{
  // 1
  StarNode *node = [StarNode node];
  [node setPosition:position];
  [node setName:@"NODE_STAR"];

  // 2
  SKSpriteNode *sprite;
  sprite = [SKSpriteNode spriteNodeWithImageNamed:@"Star"];
  [node addChild:sprite];

  // 3
  node.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:sprite.size.width/2];
	
  // 4
  node.physicsBody.dynamic = NO;

  return node;
}

The code above should all look familiar by now:

  1. You instantiate your StarNode and set its position.
  2. You then assign the star’s graphic using an SKSpriteNode.
  3. You’ve given the node a circular physics body - you use it for collision detection with other objects in the game.
  4. Finally, you make the physics body static, because you don’t want gravity or any other physics simulation to influence the stars.

Now add the following code to initWithSize:, just before where you create the player node. You want the stars to be behind the player in the foreground node and so you need to add them before you add the player node.

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

Build and run the game. Tap to start and watch the player sprite collide with the star.

11-FirstStar

The Uber Jumper bonks its head on the star. That wasn’t the plan! Why do you think that happened? Take a guess.

[spoiler title="Solution"]The physics engine handles the collision between the player and star nodes. The player node’s physics body hits the star node's physics body, which is static and thus immovable. The star node blocks the player node.[/spoiler]

Collision Handling and Bit Masks

To handle collisions between the player and star nodes, you need to capture the collision event and call the GameObjectNode method collisionWithPlayer:.

This is a good time to take a look at collision bit masks in Sprite Kit.

When setting up collision information for your physics bodies, there are three bit mask properties you can use to define the way the physics body interacts with other physics bodies in your game:

  • categoryBitMask defines the collision categories to which a physics body belongs.
  • collisionBitMask identifies the collision categories with which this physics body should collide. Here, "collide" means to bump into each other as physical objects would. For example, in a third-person shooter you may want your player sprite to collide with enemy sprites, but pass through other player sprites.
  • contactTestBitMask tells Sprite Kit that you would like to be informed when this physics body makes contact with a physics body belonging to one of the categories you specify. For example, in your game you want Sprite Kit to tell you when the player sprite touches a star or a platform. Using the correct combination of settings for the contactTestBitMask and the collisionBitMask, you can tell Sprite Kit to let objects pass through each other but still notify you when that happens so you can trigger events.

First, define your categories. Open MyScene.m and add the following typedef above the @interface section:

typedef NS_OPTIONS(uint32_t, CollisionCategory) {
  CollisionCategoryPlayer   = 0x1 << 0,
  CollisionCategoryStar     = 0x1 << 1,
  CollisionCategoryPlatform = 0x1 << 2,
};

To set up the player node’s collision behavior, add the following code to the bottom of createPlayer, just before the return statement:

// 1
playerNode.physicsBody.usesPreciseCollisionDetection = YES;
// 2
playerNode.physicsBody.categoryBitMask = CollisionCategoryPlayer;
// 3
playerNode.physicsBody.collisionBitMask = 0;
// 4
playerNode.physicsBody.contactTestBitMask = CollisionCategoryStar | CollisionCategoryPlatform;

Let’s take a closer look at this code block:

  1. Since this is a fast-moving game, you ask Sprite Kit to use precise collision detection for the player node’s physics body. After all, the gameplay for Uber Jump is all about the player node’s collisions, so you’d like it to be as accurate as possible! (it costs few more cpu cycles, but the fun is considerably more!)
  2. This defines the physics body’s category bit mask. It belongs to the CollisionCategoryPlayer category.
  3. By setting collisionBitMask to zero, you’re telling Sprite Kit that you don’t want its physics engine to simulate any collisions for the player node. That’s because you’re going to handle those collisions yourself!
  4. Here you tell Sprite Kit that you want to be informed when the player node touches any stars or platforms.

Now set up the star node. Add the following code to the bottom of createStarAtPosition:, just before the return statement:

node.physicsBody.categoryBitMask = CollisionCategoryStar;
node.physicsBody.collisionBitMask = 0;

This is similar to your setup for the player node. You assign the star's category and clear its collisionBitMask so it won't collide with anything. In this case, though, you don’t set its contactTestBitMask, which means Sprite Kit won’t notify you when something touches the star. You already instructed Sprite Kit to send notifications when the player node touches the star, which is the only contact with the star that you care about, so there's no need to send notifications from the star’s side.

Sprite Kit sends notifications for the node contacts you've registered by calling didBeginContact: on its SKContactDelegate. Set the scene itself as the delegate for the physics world by adding the SKContactDelegate protocol to the MyScene class extension in MyScene.m. It should now look like this:

@interface MyScene () <SKPhysicsContactDelegate>

Now register the scene to receive contact notifications by adding the following line to initWithSize:, just after the line that sets the gravity:

// Set contact delegate
self.physicsWorld.contactDelegate = self;

Finally, add the following method to MyScene.m to handle collision events:

- (void) didBeginContact:(SKPhysicsContact *)contact
{
  // 1
  BOOL updateHUD = NO;
  
  // 2
  SKNode *other = (contact.bodyA.node != _player) ? contact.bodyA.node : contact.bodyB.node;

  // 3
  updateHUD = [(GameObjectNode *)other collisionWithPlayer:_player];

  // Update the HUD if necessary
  if (updateHUD) {
    // 4 TODO: Update HUD in Part 2
  }
}

Let’s take a closer look at this code:

  1. You initialize the updateHUD flag, which you'll use at the end of the method to determine whether or not to update the HUD for collisions that result in points.
  2. SKPhysicsContact does not guarantee which physics body will be in bodyA and bodyB. You know that all collisions in this game will be between the player node and a GameObjectNode, so this line figures out which one is not the player node.
  3. Once you’ve identified which object is not the player, you call the GameObjectNode’s collisionWithPlayer: method.
  4. This is where you will update the HUD, if required. You'll implement the HUD in Part Two, so there's nothing but a comment here for now.

Build and run the game. Tap to start. Upon collision, the star provides the player sprite with a sizable boost and then removes itself from the scene. Good work!

12-FirstStarCollision

That was a lot of heavy lifting there! Take a well-deserved break before moving on. In the next section, you’re going to add a new star type—the uber star—as well as a cool sound effect.

Note: If you want to know more about detecting contacts and collisions in Sprite Kit feel free to check out "iOS Games by Tutorials", which contains 3 solid chapters on Sprite Kit physics.

Toby Stephens

Contributors

Toby Stephens

Author

Over 300 content creators. Join our team.