How to Make a Platform Game Like Super Mario Brothers – Part 2

Jake Gundersen
Learn how to make a game like Super Mario!

Learn how to make a game like Super Mario!

This is a blog post by iOS Tutorial Team member Jacob Gundersen, an indie game developer who runs the Indie Ambitions blog. Check out his latest app – Factor Samurai!

Welcome back to our 2-part tutorial series on making a game like Super Mario!

In the first part of the series, you learned how to create a simple, tile-based physics engine that controls how the hero of your game, Koalio, moves around his world.

In this second and final part of the series, you’ll learn how to make Koalio run and jump – the fun part of the game!

You’ll also add collisions with those scary spikey floors, handle winning and losing, and of course add some gratuitous sound effects and music!

This second part is WAY simpler (and shorter) than the first tutorial, a reward for the hard work you put in last time! So turn your coding mojo on, and enjoy!

Moving Koalio Around

The controls you’re going to implement are very simple. There will be forward and jump controls only — much like 1-bit Ninja. If you touch the left half of the screen, Koalio will run forward. Touching the right half of the screen will make Koalio jump.

You heard me right – Koalio can’t move backwards! True Koalas don’t back down from danger.

Since Koalio will be moved forward by the user, rather than by the GameLevelLayer, you need some values that you can check in the Player class to update his forward velocity. Add the following properties to the Player class (don’t forget to synthesize!):

In Player.h:

@property (nonatomic, assign) BOOL forwardMarch;
@property (nonatomic, assign) BOOL mightAsWellJump;

In Player.m:

@synthesize forwardMarch = _forwardMarch, mightAsWellJump = _mightAsWellJump;

Now add the following touch-handling methods to the GameLevelLayer:

- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {  
  for (UITouch *t in touches) {
    CGPoint touchLocation = [self convertTouchToNodeSpace:t];
    if (touchLocation.x > 240) {
      player.mightAsWellJump = YES;
    } else {
      player.forwardMarch = YES;

- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
  for (UITouch *t in touches) {
    CGPoint touchLocation = [self convertTouchToNodeSpace:t];
    //get previous touch and convert it to node space
    CGPoint previousTouchLocation = [t previousLocationInView:[t view]];
    CGSize screenSize = [[CCDirector sharedDirector] winSize];
    previousTouchLocation = ccp(previousTouchLocation.x, screenSize.height - previousTouchLocation.y);
    if (touchLocation.x > 240 && previousTouchLocation.x <= 240) {
      player.forwardMarch = NO;
      player.mightAsWellJump = YES;
    } else if (previousTouchLocation.x > 240 && touchLocation.x <=240) {
      player.forwardMarch = YES;
      player.mightAsWellJump = NO;

- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
  for (UITouch *t in touches) {
    CGPoint touchLocation = [self convertTouchToNodeSpace:t];
    if (touchLocation.x < 240) {
      player.forwardMarch = NO;
    } else {
      player.mightAsWellJump = NO;

These changes should be pretty straightforward. If the user creates a touch event that has an X-coordinate of less than 240 (half the screen), you turn on the forwardMarch boolean for the player. Otherwise (if the location of the touch event is greater than 240), simply turn on the mightAsWellJump boolean.

touchesMoved is a little more complicated, because you only want to flip the boolean values above if the touch crosses mid screen, so you have to calculate the previousTouch location as well. Other than that, you're just checking to see which direction the touch crosses and turning the appropriate boolean on or off. Finally, if the user stops touching the one side of the screen or the other, you want to turn the appropriate boolean value off.

There are a few changes that need to be made to detect touches. First, add this line to init:

	self.isTouchEnabled = YES;

You’ll need to turn on multitouch in AppDelegate.m (so as to detect a touch that directs the player to move forward and to jump at the same time). Add the following line before the [director_ pushScene: [GameLevelLayer scene]]; line:

	[glView setMultipleTouchEnabled:YES];

Now that you are passing the touches through to your player class booleans, you can add some code to the update method so that Koalio can move. Start with the forward movement. Change the update method in Player.m to the following:

-(void)update:(ccTime)dt {
    CGPoint gravity = ccp(0.0, -450.0);
    CGPoint gravityStep = ccpMult(gravity, dt);
    CGPoint forwardMove = ccp(800.0, 0.0);
    CGPoint forwardStep = ccpMult(forwardMove, dt); //1
    self.velocity = ccpAdd(self.velocity, gravityStep);
    self.velocity = ccp(self.velocity.x * 0.90, self.velocity.y); //2
    if (self.forwardMarch) {
        self.velocity = ccpAdd(self.velocity, forwardStep);
    } //3
    CGPoint minMovement = ccp(0.0, -450.0);
    CGPoint maxMovement = ccp(120.0, 250.0);
    self.velocity = ccpClamp(self.velocity, minMovement, maxMovement); //4
    CGPoint stepVelocity = ccpMult(self.velocity, dt);
    self.desiredPosition = ccpAdd(self.position, stepVelocity);

Let’s break this down section-by-section:

  1. You add a forwardMove force that will come into play while the user is touching the screen. As a reminder, you are scaling that force (800 points per second) to the appropriate amount for the current frame's timestep (dt) in order to have consistent acceleration.
  2. Here you apply a damping force to the horizontal velocity to simulate friction. You're applying physics here just as you did with gravity. In each frame, additional movement force will be applied.

    When the force is removed, you want the player to come to a stop, but not immediately. Here you apply a 0.90 damping; in other words, reducing the overall horizontal force by two percent each frame.

  3. In section three, you check for the boolean (meaning that the screen is being touched) and add the forwardMove force if appropriate.
  4. In section four, you apply the clamping. This limits the player's maximum movement speed in both the horizontal (running top speed), upward (jumping speed) and downward (falling) directions.

    These damping and clamping values put limits on how quickly things happen in the game. It also prevents the buildup of velocity problem that you experienced in the first part of the tutorial.

    You want the player to have a maximum speed and to reach that speed within a second or less. This way your player's movements still feel natural, and provide a level of control. The maximum force that you'll allow is a positive 120 value, which would be one quarter the screen width per second.

    If you want to increase the rate of acceleration of your player, increase the forwardMove variable and the damping value 0.90 respectively. If you want to increase your player’s maximum speed, simply increase the 120 value. You're also capping the jumping velocity at 250 and falling velocity at 450.

Build and run. You should be able to make Koalio run forward by pressing the left half of the screen. Watch that Koala go!

Next, you'll make him jump!

Your Mac Will Make Him... Jump, Jump!

The jump is the distinguishing feature of the platformer, and the element that leads to most of the fun. You want to make sure that the jumping movement is fluid and feels right. In this tutorial, you'll implement the jump algorithm used in Sonic the Hedgehog, as described here.

Add the following to the update method before the if (self.forwardMarch) { line:

CGPoint jumpForce = ccp(0.0, 310.0);

if (self.mightAsWellJump && self.onGround) {
    self.velocity = ccpAdd(self.velocity, jumpForce);

If you stop here (go ahead and build and run if you like), you'll get old school Atari jumping. Every jump the will be same height. You apply a force to the player, and wait until gravity pulls him back down again.

In modern platform games, users have much more control over the jumping. You want controllable, completely unrealistic (but fun as hell) Mario Bros/Sonic jumping abilities where you can change directions mid air and even stop a jump short.

To accomplish this, you’ll need to add the variable component. There are a couple ways to do it, but you'll do it the Sonic way. Set the jump algorithm to reduce the force of the upward thrust if the user stops pressing the screen. Replace the above code with the following:

  CGPoint jumpForce = ccp(0.0, 310.0);
  float jumpCutoff = 150.0;
  if (self.mightAsWellJump && self.onGround) {
    self.velocity = ccpAdd(self.velocity, jumpForce);
  } else if (!self.mightAsWellJump && self.velocity.y > jumpCutoff) {
    self.velocity = ccp(self.velocity.x, jumpCutoff);

This code performs one extra step. In the event that the user stops pressing the screen (self.mightAsWellJump will become NO), it checks the upward velocity of the player. If that value is greater than the cutoff, it will set the velocity to the cutoff value.

This effectively reduces the jump. This way, you'll always get a minimum jump (at least as high as the jumpCutoff), but if you continue to hold, you'll get the full jump force available.

Build and run now. This is starting to feel like a real game! From this point on, you'll probably need to test on a real device (if you haven't been already) in order to use both “buttons.”

You got Koalio running and jumping, but eventually he’ll run out of screen real estate. Time to fix that!

Add this snippet of code from the tile-based game tutorial to GameLevelLayer.m:

-(void)setViewpointCenter:(CGPoint) position {
  CGSize winSize = [[CCDirector sharedDirector] winSize];
  int x = MAX(position.x, winSize.width / 2);
  int y = MAX(position.y, winSize.height / 2);
  x = MIN(x, (map.mapSize.width * map.tileSize.width) 
      - winSize.width / 2);
  y = MIN(y, (map.mapSize.height * map.tileSize.height) 
      - winSize.height/2);
  CGPoint actualPosition = ccp(x, y);
  CGPoint centerOfView = ccp(winSize.width/2, winSize.height/2);
  CGPoint viewPoint = ccpSub(centerOfView, actualPosition);
  map.position = viewPoint; 

This code clamps the screen to the position of the player. In the case where Koalio is at the edge of the level, it stops centering on him, and clamps the edge of the level to the edge of the screen.

There’s one modification here from the original post. In the last line, the map is moved, instead of the layer. This is possible because the player is a child of the map, so when the player moves right, the map moves left and the player remains in the center of the screen.

The touch methods rely on a position within the layer as well. If you moved your layer around instead, you would need to take those calculations into account. This is easier.

For a complete explanation, refer to the tile-based game tutorial.

You need to add that call to the update method:

	[self setViewpointCenter:player.position];

Build and run now. You can navigate Koalio through the entire level!

The Agony of Defeat

Now you can move on to handling the winning and losing game scenarios.

Tackle the losing scenario first. There are hazards placed in this level. If the player collides with a hazard, the game will end.

Since these are fixed tiles, you need to handle them like you handled the wall collisions in the previous tutorial. However, instead of resolving collisions, you'll end the game. You're in the home stretch now — there’s only a few things left to do!

Add the following method to GameLevelLayer.m:

-(void)handleHazardCollisions:(Player *)p {
  NSArray *tiles = [self getSurroundingTilesAtPosition:p.position forLayer:hazards ];
  for (NSDictionary *dic in tiles) {
    CGRect tileRect = CGRectMake([[dic objectForKey:@"x"] floatValue], [[dic objectForKey:@"y"] floatValue], map.tileSize.width, map.tileSize.height);
    CGRect pRect = [p collisionBoundingBox];
    if ([[dic objectForKey:@"gid"] intValue] && CGRectIntersectsRect(pRect, tileRect)) {
      [self gameOver:0];

All of this code should look familiar, since it's copied and pasted from the checkAndResolveCollisions method. The only method that's new is gameOver. This call takes one parameter: 0 if the player has lost, 1 if the player has won.

You're using the hazards layer instead of the walls layer, so you'll need to set that up in the @interface at the beginning of the implementation file as an instance variable CCTMXLayer *hazards;. Set it up in init (just after the walls setup line):

	hazards = [map layerNamed:@"hazards"];

One last thing you need to do is call the method in your update method:

-(void)update:(ccTime)dt {
  [player update:dt];
  [self handleHazardCollisions:player];
  [self checkForAndResolveCollisions:player];
  [self setViewpointCenter:player.position];

Now, if the player runs into any tile from the hazards layer, you'll call gameOver. That method is just going to throw up a restart button with a message that you've lost (or won, as the case may be):

-(void)gameOver:(BOOL)won {
	gameOver = YES;
	NSString *gameText;
	if (won) {
		gameText = @"You Won!";
	} else {
		gameText = @"You have Died!";
  CCLabelTTF *diedLabel = [[CCLabelTTF alloc] initWithString:gameText fontName:@"Marker Felt" fontSize:40];
  diedLabel.position = ccp(240, 200);
  CCMoveBy *slideIn = [[CCMoveBy alloc] initWithDuration:1.0 position:ccp(0, 250)];
  CCMenuItemImage *replay = [[CCMenuItemImage alloc] initWithNormalImage:@"replay.png" selectedImage:@"replay.png" disabledImage:@"replay.png" block:^(id sender) {
    [[CCDirector sharedDirector] replaceScene:[GameLevelLayer scene]];
  NSArray *menuItems = [NSArray arrayWithObject:replay];
  CCMenu *menu = [[CCMenu alloc] initWithArray:menuItems];
  menu.position = ccp(240, -100);
  [self addChild:menu];
  [self addChild:diedLabel];
  [menu runAction:slideIn];

The first line sets a new boolean called gameOver. You use this value to stop the update method from allowing the player to continue to move and interact with the level. You’ll see that in just a minute.

Next, the code creates a label, and assigns a string based on whether the user has won or lost. It also creates a button that allows the user to start over.

These block-based methods with the CCMenu objects are really nice to use. In this case you're just replacing the scene with a new copy of itself in order to start the level over. You also use a CCAction, CCMoveBy, to animate the replay button into place, just for fun.

The only other thing you need to do is add the gameOver boolean to the GameLevelLayer class. This can just be an instance variable, since you won't need to access it outside of the calls. Add it to the @interface at the beginning of the GameLevelLayer.m, along with the variable we need to track the walls layer:

CCTMXLayer *hazards;
BOOL gameOver;

Edit the update method as follows:

-(void)update:(ccTime)dt {
  if (gameOver) {
  [player update:dt];
  [self checkForAndResolveCollisions:player];
  [self handleHazardCollisions:player];
  [self setViewpointCenter:player.position];

Go ahead and build and run now, and find some spikes to jump on! You should see something like this:

Don't repeat this too much though, or the animal protection agency might come after you! :]

The Pit of Doom

Now for the scenario where Koalio falls down a hole. In this case, you'll end the game.

As the code is now, it will crash. (Horrors!) The code throws a TMXLayer: invalid position error. That's where you need to intervene. This occurs in the getSurroundingTilesAtPosition: method when you call tileGIDAt:.

Add the following code in GameLevelLayer.m, in the getSurroundingTilesAtPosition: method, before the tileGIDat: line;

if (tilePos.y > (map.mapSize.height - 1)) {
    //fallen in a hole
    [self gameOver:0];
    return nil;

This code will run the gameOver routine and abandon the process of building the tile array. You'll also need to abandon the process of looping through the tiles in checkForAndResolveCollisions. After the NSArray *tiles = [self getSurroundingTilesAtPosition:p.position forLayer:walls ]; line, add this block of code:

  if (gameOver) {

You'll abandon that loop as well and avoid any crashing that might result from trying to run the routine with an incomplete array.

Build and run now. Find a pit to jump into, and . . . no crashing! The game ends as it should.


Now, handle the case where your hero Koalio wins the game!

All you're going to do is monitor the X-position of the player and trigger the “win” condition when he crosses the threshold (which will be at the end of the level). This level is about 3400 pixels wide. You're going to trigger a “win” condition when the player gets to pixel 3130.

Add a new method in GameLevelLayer.m as follows:

-(void)checkForWin {
  if (player.position.x > 3130.0) {
    [self gameOver:1];
-(void)update:(ccTime)dt {
  [player update:dt];
  [self handleHazardCollisions:player];
  [self checkForWin];
  [self checkForAndResolveCollisions:player];
  [self setViewpointCenter:player.position];

Build and run now. Navigate your hero Koalio through the level, and if you can make it to the end, you'll have this message:

Gratuitous Music and Sound Effects

You know what time it is - time for gratuitous music and sound effects!

Let'd dive right into it. Add this at the top of GameLevelLayer.m and Player.m:

#import "SimpleAudioEngine.h"

Then add this line to the init method of the GameLevelLayer.

	[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"level1.mp3"];

This gives you some nice gaming music. Thanks to Kevin Macleod of for composing the music (Brittle Reel). He has tons of CC licensed music there!

Now add a jumping sound. Go to the Player.m and add the following to the update method inside the jumping code:

if (self.mightAsWellJump && self.onGround) {
    self.velocity = ccpAdd(self.velocity, jumpForce);
    [[SimpleAudioEngine sharedEngine] playEffect:@"jump.wav"];
} else if (!self.mightAsWellJump && self.velocity.y > jumpCutoff) {
    self.velocity = ccp(self.velocity.x, jumpCutoff);

Finally, play a sound when Koalio falls down a hole or when he hits a hazard. Do that in the gameOver method in GameLevelLayer.m:

-(void)gameOver {
  gameOver = YES;
  [[SimpleAudioEngine sharedEngine] playEffect:@"hurt.wav"];  
  CCLabelTTF *diedLabel = [[CCLabelTTF alloc] initWithString:@"You have Died!" fontName:@"Marker Felt" fontSize:40];
  diedLabel.position = ccp(240, 200);

Build and run, and enjoy your groovy new tunes.

And, that's it. You've written a platformer. You. Are. Awesome!

Where to Go From Here?

Here's the final project file with all of the code from the tutorial.

There are a lot more things that could have been covered: everything from enemy collisions and AI, to enhanced movement abilities (wall slide, double jump, etc.), to level design guidelines.

And speaking of that... great news about that!

The Platformer Game Starter Kit

I'm happy to announce that all of this and more will be included in the upcoming Platformer Game Starter Kit! Here's a quick video showing you the game you'll make in the Starter Kit:

Here's what you'll learn in the Platformer Game Starter Kit:

  • How to manage and load multiple levels
  • How to make a scrollable, unlockable level selection screen
  • How to integrate UIKit Storyboards with Cocos2D
  • How to efficiently use sprite sheets, animations, tilesets, and work with pixel art!
  • How to create a state machine to handle character/enemy animations and behaviors
  • More about how to make amazing and fun tile-based physics engines!
  • How to create an on-screen joystick and HUD
  • How to add iCade support
  • Level design for platformers
  • How to build Enemy AI and dynamic behaviors.
  • How to add an EPIC Boss fight!
  • Interviews with the developers of several top iOS platformer games with tips and tricks
  • . . . and lots, lots more!

If you're interested in the Platformer Game Starter Kit, make sure you're signed up for Ray's monthly newsletter - we'll announce it there when it comes out! :]

Update 4/8/13: At long last, the Platformer Game Starter Kit is now available! Check it out on the store.

In the meantime, don’t forget the resources recommended at the end of Part 1 of this tutorial.

I hope you enjoyed getting your physics on and are more inspired than ever about building your own platformer!

This is a blog post by iOS Tutorial Team member Jacob Gundersen, an indie game developer who runs the Indie Ambitions blog. Check out his latest app - Factor Samurai!

Jake Gundersen

Jacob is an indie game developer and runs the Indie Ambitions blog. Check out his latest app - Factor Samurai! You can find him on Twitter.

Other Items of Interest

Save time.
Learn more with our video courses. Weekly

Sign up to receive the latest tutorials from each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 27 total!

iOS Team

... 83 total!

Android Team

... 44 total!

Unity Team

... 16 total!

Articles Team

... 4 total!

Resident Authors Team

... 32 total!

Podcast Team

... 8 total!

Recruitment Team

... 8 total!

Illustration Team

... 4 total!