Sprite Kit Tutorial: How to Make a Platform Game Like Super Mario Brothers – Part 1

Learn how to make a platform game like Super Mario Brothers in this Sprite Kit tutorial! 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 checkForAndResolveCollisionsForPlayer:forLayer: method…

  1. In section 2 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.
  2. In section 3, you determine whether the collision is wide or tall. If it's wide, you resolve vertically.
  3. Next you create a variable to hold the value of the distance that you need to move the Player to no longer be in a collision state with the tile.
  4. You'll either be moving the Koala up or down, which you determine next by seeing if the tile index is greater than five (tile indices 6 and 8 are both beneath). 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.
  5. Finally, you set the position of the Koala to the final collision-detection resolved 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! You can remove or comment out the log statement in the checkForAndResolveCollisionsForPlayer:forLayer:

//NSLog(@"GID %ld, Tile Coord %@, Tile Rect %@, player rect %@", (long)gid, NSStringFromCGPoint(tileCoord), NSStringFromCGRect(tileRect), NSStringFromCGRect(playerRect));

Build and run. Are you surprised by the results?

Eventual Gravity

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.

Here's the solution: You are constantly increasing the speed of 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 checkForAndResolveCollisionsForPlayer:forLayer:

- (void)checkForAndResolveCollisionsForPlayer:(Player *)player forLayer:(TMXLayer *)layer
{
  NSInteger indices[8] = {7, 1, 3, 5, 0, 2, 6, 8};
  player.onGround = NO;  ////Here
  for (NSUInteger i = 0; i < 8; i++) {
    NSInteger tileIndex = indices[i];
    
    CGRect playerRect = [player collisionBoundingBox];
    CGPoint playerCoord = [layer coordForPoint:player.desiredPosition];
    
    NSInteger tileColumn = tileIndex % 3;
    NSInteger tileRow = tileIndex / 3;
    CGPoint tileCoord = CGPointMake(playerCoord.x + (tileColumn - 1), playerCoord.y + (tileRow - 1));
    
    NSInteger gid = [self tileGIDAtTileCoord:tileCoord forLayer:layer];
    if (gid != 0) {
      CGRect tileRect = [self tileRectFromTileCoords:tileCoord];
      //NSLog(@"GID %ld, Tile Coord %@, Tile Rect %@, player rect %@", (long)gid, NSStringFromCGPoint(tileCoord), NSStringFromCGRect(tileRect), NSStringFromCGRect(playerRect));
      //1
      if (CGRectIntersectsRect(playerRect, tileRect)) {
        CGRect intersection = CGRectIntersection(playerRect, tileRect);
        //2
        if (tileIndex == 7) {
          //tile is directly below Koala
          player.desiredPosition = CGPointMake(player.desiredPosition.x, player.desiredPosition.y + intersection.size.height);
          player.velocity = CGPointMake(player.velocity.x, 0.0); ////Here
          player.onGround = YES; ////Here
        } else if (tileIndex == 1) {
          //tile is directly above Koala
          player.desiredPosition = CGPointMake(player.desiredPosition.x, player.desiredPosition.y - intersection.size.height);
        } else if (tileIndex == 3) {
          //tile is left of Koala
          player.desiredPosition = CGPointMake(player.desiredPosition.x + intersection.size.width, player.desiredPosition.y);
        } else if (tileIndex == 5) {
          //tile is right of Koala
          player.desiredPosition = CGPointMake(player.desiredPosition.x - intersection.size.width, player.desiredPosition.y);
          //3
        } else {
          if (intersection.size.width > intersection.size.height) {
            //tile is diagonal, but resolving collision vertically
            //4
            player.velocity = CGPointMake(player.velocity.x, 0.0); ////Here
            float intersectionHeight;
            if (tileIndex > 4) {
              intersectionHeight = intersection.size.height;
              player.onGround = YES; ////Here
            } else {
              intersectionHeight = -intersection.size.height;
            }
            player.desiredPosition = CGPointMake(player.desiredPosition.x, player.desiredPosition.y + intersection.size.height );
          } else {
            //tile is diagonal, but resolving horizontally
            float intersectionWidth;
            if (tileIndex == 6 || tileIndex == 0) {
              intersectionWidth = intersection.size.width;
            } else {
              intersectionWidth = -intersection.size.width;
            }
            //5
            player.desiredPosition = CGPointMake(player.desiredPosition.x  + intersectionWidth, player.desiredPosition.y);
          }
        }
      }
    }
  }
  //6
  player.position = player.desiredPosition;
}

Each time the Koala has a tile under him (either adjacently or diagonally) you set the player.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 in Player.h:

@property (nonatomic, assign) BOOL onGround;

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

Solid Ground

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!

Jake Gundersen

Contributors

Jake Gundersen

Author

Over 300 content creators. Join our team.