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

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! For many of us, Super Mario Brothers was the first game that made us drop our jaws in gaming excitement. Although video games started with […] By Jake Gundersen.

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

Back to the Code!

Returning to the monster checkForAndResolveCollisions: method…

  1. Section 6 has you getting the index of the current tile. You use the index to determine the position of the tile. You are going to deal with the adjacent tiles individually, moving the Koala by subtracting or adding the width or height of the collision as appropriate. Simple enough. However, once you get to the diagonal tiles, you're going to implement the algorithm described in the previous section.In section 7, you determine whether the collision is wide or tall. If it's wide, you resolve vertically. You'll either be moving the Koala up or down, which you determine next by seeing if the tile index is greater than five (tiles six and seven are beneath the Koala). Based on that, you know whether you need to add or subtract the collision height from the Koala. The horizontal collision resolution follows the same logic.
  2. Finally, you set the position of the Koala to the final collision-detection fixed result.

That method is the guts of your collision detection system. It's a basic system, and you may find that if your game moves very quickly or has other goals, you need to alter it to get consistent results. At the end of this article, there are a couple of really great resources with more about how to handle collision detection.

Let's put it to use! Make the following change to the update method (still in GameLevelLayer:)

// Replace this line: "[self getSurroundingTilesAtPosition:player.position forLayer:walls];" with:
[self checkForAndResolveCollisions:player]; 

Also, you can remove or comment out the log statement in the getSurroundingTilesAtPosition:forLayer:

	/*
  for (NSDictionary *d in gids) {
    NSLog(@"%@", d);
  } //8 */

Build and run! Are you surprised by the results?

Koalio is stopped by the floor, but sinks into it eventually! What gives?

Can you guess what’s been missed? Remember — you are adding the force of gravity to the Koala’s velocity with each frame. This means that Koalio is constantly accelerating downward.

You are constantly adding speed to the Koala's downward trajectory until it is greater than the size of the tile — you’re moving through an entire tile in a single frame, which was a problem discussed earlier.

When you resolve a collision, you also need to reset the velocity of the Koala to zero for that dimension! The Koala has stopped moving, so the velocity value should reflect it.

If you don't do this, you'll get weird behaviors, both moving through tiles as you saw above, and also in situations where your Koala jumps into a low ceiling and he floats against it longer than he should. This is the kind of weirdness you want to avoid in your game.

It was mentioned before that you need a good way to determine when the Koala is on the ground so he can't jump off of thin air. You'll set that flag up now. Add the indicated lines to the checkForAndResolveCollisions:

-(void)checkForAndResolveCollisions:(Player *)p {
  
  NSArray *tiles = [self getSurroundingTilesAtPosition:p.position forLayer:walls ]; //1
  
  p.onGround = NO; //////Here

  for (NSDictionary *dic in tiles) {
    CGRect pRect = [p collisionBoundingBox]; //3
    
    int gid = [[dic objectForKey:@"gid"] intValue]; //4
    if (gid) {
      CGRect tileRect = CGRectMake([[dic objectForKey:@"x"] floatValue], [[dic objectForKey:@"y"] floatValue], map.tileSize.width, map.tileSize.height); //5
      if (CGRectIntersectsRect(pRect, tileRect)) {
        CGRect intersection = CGRectIntersection(pRect, tileRect);
        int tileIndx = [tiles indexOfObject:dic];

        if (tileIndx == 0) {
          //tile is directly below player
          p.desiredPosition = ccp(p.desiredPosition.x, p.desiredPosition.y + intersection.size.height);
          p.velocity = ccp(p.velocity.x, 0.0); //////Here
          p.onGround = YES; //////Here
        } else if (tileIndx == 1) {
          //tile is directly above player
          p.desiredPosition = ccp(p.desiredPosition.x, p.desiredPosition.y - intersection.size.height);
          p.velocity = ccp(p.velocity.x, 0.0); //////Here
        } else if (tileIndx == 2) {
          //tile is left of player
          p.desiredPosition = ccp(p.desiredPosition.x + intersection.size.width, p.desiredPosition.y);
        } else if (tileIndx == 3) {
          //tile is right of player
          p.desiredPosition = ccp(p.desiredPosition.x - intersection.size.width, p.desiredPosition.y);
        } else {
          if (intersection.size.width > intersection.size.height) {
            //tile is diagonal, but resolving collision vertially
			p.velocity = ccp(p.velocity.x, 0.0); //////Here
            float resolutionHeight;
            if (tileIndx > 5) {
              resolutionHeight = intersection.size.height;
              p.onGround = YES; //////Here
            } else {
              resolutionHeight = -intersection.size.height;
            }
            p.desiredPosition = ccp(p.desiredPosition.x, p.desiredPosition.y + resolutionHeight);
            
          } else {
            float resolutionWidth;
            if (tileIndx == 6 || tileIndx == 4) {
              resolutionWidth = intersection.size.width;
            } else {
              resolutionWidth = -intersection.size.width;
            }
            p.desiredPosition = ccp(p.desiredPosition.x + resolutionWidth, p.desiredPosition.y);
          } 
        } 
      }
    } 
  }
  p.position = p.desiredPosition; //8
}

Each time the Koala has a tile under him (either adjacently or diagonally) you set the p.onGround to YES and set his velocity to zero. Also, if the Koala has a tile adjacently above him, you set his velocity to zero. This will make the velocity variable properly reflect the Koala’s actual movement and speed.

You set the onGround flag to NO at the beginning of the loop. This way, the only time the onGround returns a YES is if you have detected a collision underneath the Koala. You can use this to know when the Koala can and cannot jump. You need to add this property to the Koala class, so do that now.

Add the following property to the header file (and then synthesize it in the implementation). In Player.h:

@property (nonatomic, assign) BOOL onGround;

And in Player.m:

@synthesize onGround = _onGround;

Build and run! Is it working as expected? Yes! O frabjous day! Callooh! Callay!

Where to Go From Here?

Congratulations! You've built yourself a working physics engine! If you're through to this point, you can breathe a sigh of relief and pat yourself on the back. That was the hard part – nothing but smooth sailing in Part 2 of this tutorial!

Here's the complete project you have built so far.

In Part 2, you'll make your hero Koalio run and jump. You'll also make the spikes on the floor dangerous, and handling winning/losing the game.

If you want more information about platformers and physics engines, here are a few resources I recommend:

Let me know how it’s going so far by submitting your comments to the forums!


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

Contributors

Jake Gundersen

Author

Over 300 content creators. Join our team.