
Welcome to the second part of the tutorial series on how to write your own platformer like Super Mario Brothers!
In the
first part, we wrote a simple physics engine based on Tiled Map.
In the second (and last) part, we will teach Koalio to move and jump - the most fun part of any platformer!
We will learn to track collisions with dangers at the level, to handle victory and defeat; Add great sound effects and music!
The second part is an order of magnitude easier (and shorter) than the first - a little rest after hard work last time! So turn on your kodo kung fu and enjoy!
Move Koalio
Our movements will be very simple: only running forward and jumping - just like in 1-bit Ninja. If the player touches the left side of the screen, Koalio runs forward. If the player touches the right side of the screen, Koalio jumps.
You all understood correctly: Koalio can not move back! Real Koalas do not back away from danger!
Since Koalio will move forward, we need a variable that can be used in the Player class to update Koala's position. Add the following to the Player class:
')
In
Player.h :
@property (nonatomic, assign) BOOL forwardMarch; @property (nonatomic, assign) BOOL mightAsWellJump;
And we synthesize everything in
Player.m :
@synthesize forwardMarch = _forwardMarch, mightAsWellJump = _mightAsWellJump;
Now add the following touch handling methods to the
GameLevelLayer :
Click me - (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];
The code above is quite simple to understand. If the X-coordinate of the touch screen is less than 240 (half of the screen), then we assign the variable forwardMarch to the value YES. Otherwise (if the X-coordinate is greater than 240), assign the YES value to the variable mightAsWellJump.
touchesMoved is a slightly more complicated method. We need to change the value of variables only when the touch crosses the middle of the screen. So we must also take into account the previous touch - the previousTouch variable. In other words, we simply check from which side the intersection occurred and change the values ​​of the Boolean variables, respectively.
We want, when the player ceases to touch a certain area of ​​the screen, only the necessary Boolean variables are turned off.
However, it is worth changing a few more things to properly handle the touches. Add the following line to the
init method:
self.isTouchEnabled = YES;
We need to enable multitouch in
AppDelegate.m (suddenly, our player wants to jump and move forward at the same time). Add the following before the line
[director_ pushScene: [GameLevelLayer scene]]; :
[glView setMultipleTouchEnabled:YES];
Now that we have the value of the variables in the object of the Player class, we can add some code to the
update method so Koalio can move. Let's start with moving forward. Change the
update method in
Player.m to the following:
Click me -(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);
Let's take this code step by step:
- We add a forwardMove force that applies when the player touches the screen. Let me remind you that we change this force (800 points per second) depending on the frame rate per second in order to get a constant acceleration.
- Here we add friction. We use physics as we did with gravity. Forces will be applied every frame. When the power runs out, we want the player to stop. We apply friction force of 0.90. In other words, we reduce the power of movement by 10% each frame.
- In the third section, we check if the player is touching the screen and add, if necessary, the force of forwardStep.
- In section four, we set limits. We limit the maximum speed of the player. Both horizontal (running speed) and vertical (jump speed, gravity speed).
The force of friction and limitations allow us to control the speed of the processes in the game. Having these constraints prevents a problem with too high a speed from the first tutorial.
We want the player to reach maximum speed in a second or less. So, Koal's movements will still look natural and, at the same time, resist control. We will set the limit to 120 (a quarter of the screen width per second) for maximum speed.
If we want to increase the acceleration bar of our player, we must increase both values ​​respectively: forwardMove and friction force (0.90). If we want to increase the maximum speed of the player, we just need to increase the value of 120. We also limit the jump speed to 250 and the fall speed to 450.
Run the project. Koalio should start moving if you tap the left side of the screen. See how our Koala moves briskly!

Next we will make Koala jump!
Our poppy will make her ... Jump, jump!
Jumping is an integral part of any platformer that brings quite a lot of fun. We must make sure that the jump is smooth and well calibrated. In this tutorial we use the Sonic the Hedgehog jump algorithm described
here .
Add the following to the
update method before the line
if (self.forwardMarch) { :
CGPoint jumpForce = ccp(0.0, 310.0); if (self.mightAsWellJump && self.onGround) { self.velocity = ccpAdd(self.velocity, jumpForce); }
If we stop right now and run the project, we will get classic Atari style jumps. All jumps will be the same height. We apply force to Koala and wait until gravity overcomes it.
In modern platformers, users have much more control over jumps. We want controlled, totally unrealistic (but damn fun) jumping in the style of Mario Bros or Sonic.
There are several ways to achieve our goal, but we take Sonic as a basis for jumping. When the player ceases to touch the screen, the force of the jump begins to decrease. Replace the code above 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 another additional step. When the player stops touching the screen (the self.mightAsWellJump value becomes NO), the game checks the Koalio's upward velocity. If this value is greater than our threshold, then the game sets the variable speed threshold value.
Thus, when you instantly touch the screen, the Koala will not jump higher than jumpCutoff, but by long pressing the screen, the Koala will jump higher and higher.
Run the project. The game starts to sound like something worthwhile! Now, you most likely need to test the application on a real device (if you have not already done so) to test the operation of both “buttons”.

We made Koalio run and jump, but he rather quickly runs off the edges of the screen. It's time to fix it!
Add the following snippet from the
tutorial on cell maps in
GameLevelLayer.m :
Click me - (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 assigns the screen to the player’s position. In case Koalio reaches the end of the level, the screen ceases to be centered on the Koala and fixes the edge of the level beyond the edge of the screen.
It is worth noting that in the last line we move the card, not the player. This is possible because the player is a descendant of the card. So, when the player moves to the right, the card moves to the left and the player remains in the center of the screen.
For a more detailed explanation, visit
this tutorial .
We need to add the following call to the
update method:
[self setViewpointCenter:player.position];
Run the project. Koalio can run through the entire level!

The agony of defeat
There are two outcomes of the game: either victory or defeat.
First, let's deal with the scenario of defeat. At our level there are hazards. If a player faces danger, the game ends.
Since the dangers are fixed cells, we will work with them as with obstacles from the past tutorial. However, instead of defining collisions, we will simply end the game. We are on the road to success - just a little bit left!
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 the code above should be familiar to you, as it is simply copied from the checkAndResolveCollisions method. The only innovation is the gameOver method. We send him 0 if the player lost, and 1 if he won.
We use the hazards layer instead of the walls layer, so we need to declare the variable
CCTMXLayer * hazards; in
@ interface
at the beginning of the executive file. Add this to the
init method (immediately after declaring a variable for the walls layer):
hazards = [map layerNamed:@"hazards"];
The only thing left to do is add the following code to the
update method:
- (void)update:(ccTime)dt { [player update:dt]; [self handleHazardCollisions:player]; [self checkForAndResolveCollisions:player]; [self setViewpointCenter:player.position]; }
Now, if a player flies into any dangerous cell with a layer of hazards, we will call gameOver. This method will simply display the “Restart” button with a message that you lost (or won - this also happens):
Click me - (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 declares the gameOver variable. We will use this variable in order to force the update method to stop making any changes in Koalio's position. Literally in a minute we will realize it.
Next, the code creates the text (label) and gives it the value of won or lost. We also create a “Restart” button.
These block-based methods of CCMenu objects are quite convenient to use. In our case, we simply replace our level with a copy of it in order to start the level again. We use CCAction and CCMove just to beautifully animate the buttons.
Another thing to do is add the gameOver boolean variable to the GameLevelLayer class. This will be a local variable, since we will not use it outside the class. Add the following to the
@ interface
at the beginning of the
GameLevelLayer.m file:
CCTMXLayer *hazards; BOOL gameOver;
Modify the
update method as follows:
- (void)update:(ccTime)dt { if (gameOver) { return; } [player update:dt]; [self checkForAndResolveCollisions:player]; [self handleHazardCollisions:player]; [self setViewpointCenter:player.position]; }
Start the game, find the studded floor and jump on it! You should see something like this:

Just do not repeat it too often, but there may be problems with animal advocates! :]
Death pit
Now add the script to drop into the hole between the cells. In this case, we just complete the game.
Right now, when falling, the code will be brightened up with the error "TMXLayer: invalid position". (Horror!) This is where we need to intervene.
Add the following code in
GameLevelLayer.m , to the
getSurroundingTilesAtPosition: method, in front of the
tileGIDat line
: if (tilePos.y > (map.mapSize.height - 1)) {
This code will trigger the gameOver action and prevent the process of creating an array of cells. We also need to prevent the process of looping through all cells in
checkForAndResolveCollisions . Add a block of code after the line
NSArray * tiles = [self getSurroundingTilesAtPosition: p.position forLayer: walls]; :
if (gameOver) { return; }
This will prevent the cycle call and unnecessary crash of the game.
Launch the game right now. Find the abyss into which you can jump, and ... no departures! The game works as intended.

Victory!
Now, let's work on the occasion when our Koalio wins the game!

All that we do is monitor the x-position of our Koala and show the screen with the winning message if it crosses a certain label. The length of this level is 3400 pixels. We put a winning mark at a distance of 3130 pixels.
Add a new method to
GameLevelLayer.m and update the update method:
- (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]; }
Run, try to win. If it works, you should see the following:

Great music and sound effects
It's time for great music and sound effects!
Let's start. Add the following to the top of
GameLevelLayer.m and
Player.m :
#import "SimpleAudioEngine.h"
Now add the following line to the init method of the
GameLevelLayer file.
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"level1.mp3"];
We just hooked up nice gaming music. Thanks to Kevin from Incompetech.com for writing music (
Brittle Reel ). He has a whole cloud of CC licensed music there!
Now add a jump sound. Go to
Player.m and add the following to the
update method jump 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, we lose the sound of defeat, when Koalio touches a dangerous cell or falls into a pit. Add the following to the
gameOver method of the
GameLevelLayer.m file:
- (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);
Launch and enjoy the new pleasant melodies.
That's all! You wrote a platformer! You are wonderful!
And here is the
source code of our finished project.
Translator's Note
I decided from time to time to translate tutorials from the site
raywenderlich.com .
My next goal is to translate a
series of tutorials on how to create your own game like Fruit Ninja based on Box2D and Cocos2D.
Write in the comments, is it worth it. If the translation will be in demand, I will translate.
About all inaccuracies and typographical errors found, please write in habraposhta or here in the comments.
We are happy to answer all questions on the tutorial!