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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How to Make a Platform Game Like Super Mario Brothers – Part 1
55 mins
- Getting Started
- The Tao of Physics Engines
- Physics Engineering
- Loading the TMXTiledMap
- The Gravity of Koalio’s Situation
- Playing God
- The Law of the Land: CGPoints and Forces
- Bumps In The Night – Collision Detection
- Heavy Lifting
- I’m Surrounded By Tiles!
- Taking Away Your Koala’s Privileges
- Let’s Resolve Some Collisions!
- Pausing to Consider a Dilemma...
- Back to the Code!
- Where to Go From Here?
Back to the Code!
Returning to the monster checkForAndResolveCollisions: method…
- 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.
- 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:
- The Sonic the Hedgehog Wiki has a great section describing how Sonic interacts with solid tiles.
- Perhaps the best guide to implementing platformers, from Higher-Order Fun.
- The creators of N have a great tutorial that goes well beyond the basics of tile-based collision systems.
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!