How to Make a Game Like Jetpack Joyride using LevelHelper and SpriteHelper [Cocos2D Edition] – Part 4

Bogdan Vladu

This is a post by special contributor Bogdan Vladu, an iOS application developer and aspiring game developer living in Bucharest, Romania.

Create a game like Jetpack Joyride with LevelHelper and SpriteHelper!

Create a game like Jetpack Joyride with LevelHelper and SpriteHelper!

Update 1/9/2013: This tutorial is now deprecated. We now have a newer, updated version of this tutorial, check it out!

Hello again, and welcome to the fourth and final installment of the tutorial series on LevelHelper and SpriteHelper. I said by the end of this series you would have an exciting game to play, and we’re almost there!

Last time, in Part Three, we came close to completing our game. We fully implemented collisions between the player and the other game elements, enabling the player to die and score points. We also made the player fly and added some nice sounds and animations.

But there are still a few improvements we can make. For one thing, we need a way to keep track of the score, and while we’re doing that we might as well add some cute moving bunnies that will be worth more points.

Our game looks nice, but I’ll take you through adding a background layer to give the illusion of a never-ending outdoor scene visible through the windows. We’ll make the background layer move so that the scene through the windows is always slightly different.

And wouldn’t it be interesting if we made some of our lasers rotate? Keep reading to complete the game!

Getting Started: Rotating Lasers

We’re picking up with the project as we left it at the end of Part Three. You can download that version of the project here.

The first thing we’re going to do is make our lasers rotate. We can’t have this game be too easy! So open up your project in LevelHelper, and make sure you have the latest version of your level open.

The first step is to go to Define Tags and define a new tag. Call it ROTATING_LASERS.

We will use this new tag to reference the lasers we select to rotate (not all of them… we’re not that evil, are we?) when we implement the rotation via code.

Now select a couple of the lasers in the level. Make sure the lasers you select have room to rotate. Assign the ROTATING_LASERS tag to these lasers.

Once you’re done tagging the lasers, save the level and open your Xcode project. Navigate to HelloWorldScene.h and add the following in the class definition:

NSArray* rotatingLasers;

Then place the following at the end the retrieveRequiredObjects method in HelloWorldScene.mm:

rotatingLasers = [lh spritesWithTag:ROTATING_LASERS];
[rotatingLasers retain];

Here we take reference to all the sprites that have the tag ROTATING_LASERS. Because the array returned by this method is autoreleased, we need to retain it. In order to not forget to release this retained array, go to the dealloc function and add the following before the LevelHelperLoader object release:

[rotatingLasers release];

Now that we’ve grabbed the lasers that we want, let’s make them rotate! Go inside your tick method and write this at the end:

for(LHSprite* laser in rotatingLasers)
{
    [laser transformRotation:[laser rotation]+1];
}

If you run the game now, the chosen lasers will rotate, but the player won’t die when it makes contact with them. This is because we haven’t registered a collision event for the ROTATING_LASERS tag. To do that, add the following inside the setupCollisionHandling method:

[lh registerPreCollisionCallbackBetweenTagA:PLAYER andTagB:ROTATING_LASERS idListener:self selListener:@selector(mouseLaserCollision:)];

Running the game now, the lasers will rotate and kill the player just as the non-rotating lasers do. Congrats, you have just added another layer of difficulty to our game. That said, you may want to play-test the game some more at this point to make sure the player can still get through the level!

Moving Bunnies

Running and flying over sleeping cats and dogs and dodging rotating lasers is all fun, but let’s add another reward component to the game to give it more complexity. I have just the ticket: bunnies. Cute, helpless bunnies that our player can kill for points.

Open SpriteHelper, and go to File\New. Then open Finder and navigate to the ArtPack folder (download it from Part One if you don’t have it), select the bunnies images and drag them into the SpriteHelper window. Then click the “Pack Sprites” button.

Now select all the sprites in the list and enable the “Is Sensor” option from the Physic Properties menu. (Note that our other reward component, coins, are also sensors.)

Next, create the following two animations using the bunny frames:

Animation Name: BunnyRun
Speed: 0.400
StartAtLaunch: YES
LoopForever: YES
Frames: bunny_1, bunny2

Animation Name: BunnyDie
Speed: 0.400
StartAtLaunch: YES
LoopForever: NO
Frames: bunny__die_1, bunny_die_2

When you’re done creating the animations, save the scene inside the Images folder of your Xcode project.

Going back to LevelHelper, we can see the bunny animations inside the Animations section. Select the BunnyRun animation and drag it into your level.

We now have a bunny in the level, but it will not be visible because we need to move him with the
parallax.

To add the bunny to the parallax, go to the Parallax tab, select our parallax and add the bunny sprite to it. Set the x component of the movement ratio to 1 so that the bunny moves at the same speed as our level.

If we run the level in Scene Tester we cannot see the bunny. This is because we need to set the batch z order on the bunny image.

Navigate the the Images section and set the batch z on the bunny image to 3. This will put the bunny in front of everything except the player.

Running the level again in Scene Tester, we can see the bunny moving with the parallax, but the bunny appears to be moving in place. We want the bunny to move from right to left and from left to right. To do this, we have to make him move on a path.

Navigate to the Beziers section and click NEW to create a new bezier that will describe the path of the bunny.

Click within the level view to draw the bunny’s path, and click the Finish button when you’re happy with the bezier.

Now select the “Path” option in the bezier properties to tell LevelHelper to treat that bezier as a path.

Note:

  • By default, beziers are a simple line. You can make them true beziers by deselecting “Line” in the properties menu.
  • You can edit a bezier by pressing the Edit button and then dragging the points.
  • While editing the bezier, you can create a new point by holding COMMAND and clicking on a line of the bezier.
  • While editing the bezier you can remove a point by holding ALT (Option) and clicking on a point.

Well, now we have our path defined, but the bunny sprite doesn’t know that it should move on that path. Let’s make the bunny aware of this.

Select the bunny, then in the General Properties section, click on Path.

In the Path Settings dialogue, select the bezier that describes the path from the list, then make the motion cyclic by selecting “Cyclic Motion.”

Set the amount of time you want the bunny to take to move from one end of the bezier to the other. Then select “Flip Sprite X At Ends” so that the bunny is always facing in the direction it is moving. Otherwise, it will appear to be moving backwards half the time.

If you run the level inside Scene Tester now, the bunny should move on the path and also move with the parallax. This is cool, eh? LevelHelper is the only editor that lets you move sprites on a path and with a continuos scrolling parallax at the same time.

The last step in LevelHelper is to define a new tag called “BUNNY” and assign it to the bunny sprite. Once you’ve done this, save the level.

Time to move back to our Xcode project. We have created a new image: the bunny. So we have to add the new image files to the Images folder inside Xcode.

The contents of your project’s Images folder should now look like this:

Now let’s write some code that will make the bunny die when the player collides with it.

Add the following at the end of the setupCollisionHandling method:

[lh registerBeginOrEndCollisionCallbackBetweenTagA:PLAYER andTagB:BUNNY idListener:self selListener:@selector(mouseBunnyCollision:)];

This informs LevelHelper of the collision callback, but we also need to define the method for the callback. Add the following before the setupCollisionHandling method:

-(void)mouseBunnyCollision:(LHContactInfo*)contact
{    
    if(playerIsDead)
        return;
 
    LHSprite* bunny = [contact spriteB];
 
    if(nil != bunny)
    {
        if([[bunny animationName] isEqualToString:@"bunnyRun"])
        {
            [self scoreHitAtPosition:[bunny position] withPoints:500];
            [[SimpleAudioEngine sharedEngine] playEffect:@"bunnyHit.wav"];
        }
 
        [bunny startAnimationNamed:@"bunnyDie"];
 
        [[bunny pathNode] setPaused:YES];
    }
}

In the above code, we test for a dead player and do nothing if that’s the case. We then take the bunny sprite from the contact and test if it’s valid and whether it’s running the “bunnyRun” animation.

If the bunny sprite meets both conditions, we give 500 points to the user and play the bunny hit sound (part of the sound pack downloaded in Part Three of this series). We then start the bunnyDie animation on the bunny sprite and stop the sprite’s movement on the path.

If you run the game now, you’ll see that the bunny dies when he collides with the mouse. But if you loop through the entire level, you’ll see that the bunny you killed doesn’t reappear in the level: it stays dead! So let’s do the same thing we did with the coins in Part Three and reset the bunny.

Find your spriteInParallaxHasReset method and modify it to look like this:

-(void) spriteInParallaxHasReset:(LHSprite*)sprite
{    
    if(COIN == [sprite tag]){
        [sprite setVisible:YES];
    }
    else if(BUNNY == [sprite tag]){        
        [sprite startAnimationNamed:@"bunnyRun"];        
        [[sprite pathNode] setPaused:false];
    }    
}

Above, we simply check for the BUNNY tag, and when we find a sprite that is a bunny, we set its animation to bunnyRun and unpause its path movement.

The complete project up to this point can be downloaded here.

Displaying the Score

We’re going to implement a dynamic way of showing the score. Instead of having a fixed display at the top or bottom of the screen, we’re going to show the score in a temporary pop-up window each time the user scores.

Begin by adding the following inside HelloWorldScene.h:

CCLabelTTF *scoreText;

Now define this new method somewhere before the init method inside HelloWorldScene.mm:

-(void) setupScore
{
    score = 0;
 
    CGSize winSize = [[CCDirector sharedDirector] winSize];
    scoreText = [CCLabelTTF labelWithString:@"Score: 0" dimensions:CGSizeMake(200, 50) alignment:UITextAlignmentLeft fontName:@"Arial" fontSize:22];
    scoreText.color = ccWHITE;
    scoreText.position = ccp(100, winSize.height-25);
    [self addChild:scoreText z:20];    
}

Here we create a new label and assign it to our scoreText variable. We then add this label to the Cocos layer, in our case “self.” And we set the label position to be the top left corner.

Now call this new method inside the init method right after the call to setupAudio:

[self setupScore];

This is good, but right now we’re not showing the actual score to the user. We’re just showing some text. Let’s make the text change when the user gets points.

Modify your scoreHitAtPosition method to look like this:

-(void)scoreHitAtPosition:(CGPoint)position withPoints:(int)points
{
    score += points;
    [scoreText setString:[NSString stringWithFormat:@"Score: %d", score]];
 
    NSString* curScoreTxt = [NSString stringWithFormat:@"+ %d", points];
    CCLabelTTF *curScore = [CCLabelTTF labelWithString:curScoreTxt fontName:@"Marker Felt" fontSize:24];
    curScore.color = ccWHITE;
    curScore.position = position;
    [self addChild:curScore z:20];
 
    id opacityAct1 = [CCActionTween actionWithDuration:1 key:@"opacity" from:255 to:0];
    id actionCallFunc = [CCCallFuncN actionWithTarget:self selector:@selector(removeScoreText:)];
    id seq = [CCSequence actionOne:opacityAct1 two:actionCallFunc];
    [curScore runAction:seq];
}

Here we add the new points to the score variable. Then we set the text of our score label to be the newly updated score.

We then create a new label that will be displayed at the position we get in the attribute of this method. As shown in the code, this is the position of the coin or bunny triggering the score update. The coin or bunny vanishes upon collision with the player, and a score display temporarily appears in its place.

We add the new label to the layer (in this case self). We then create two actions. The first action makes the new score label disappear in one second by changing the opacity property of the sprite from 255 (full visibility) to 0 (not visible). The second action calls a new method, removeScoreText.

Lastly, we create a sequence using the two defined actions and run that sequence on the new label sprite.

We still need to define the removeScoreText method called by the second action. Add the following above the scoreHitAtPosition method:

-(void)removeScoreText:(CCLabelTTF*)scoreLabel
{
    [scoreLabel removeFromParentAndCleanup:YES];
}

This new method just removes the score display from the layer.

Run the game now and see how nicely this works. Every time the player makes contact with a coin or a bunny, a label pops up showing the updated score, displays for one second, then disappears.

Score displayed in Rocket Mouse

Adding An Outside Layer

Now for our last tweak: an “outdoor” scene that will be visible scrolling through the windows in the level, adding a nice sense of depth to the gameplay.

Open SpriteHelper and create a new scene with File\New. Then open Finder, navigate to the ArtPack folder, select the bgWindow, window_background, and window_foreground images, and drag then on the SpriteHelper window.

Press Pack Sprites to pack the new sprites. Because one of the images doesn’t have alpha, a new window will ask you if you want to crop the image using a selected color. Choose Don’t Crop.

Select all the sprites and set the “No Physics” option from the Physics tab. Save the scene as “outside” and place it in the Images folder.

Back in LevelHelper, let’s create a new level in order to see the steps. With level03 opened, press Command-Shift-S to save the level as a new level. Give it the name level04 and save it inside the Levels folder.

Now inside the level view, select all the window sprites as well as all the background sprites from under the windows.

Delete the sprite by pressing the Delete key or clicking the red minus sign button on the left.

After the removal of these sprites, the scene should look something like this:

Double-check that you’ve removed all of the window and under-window background sprites, and save the scene.

Now filter the outside sprites by checking the “filter” option next to outside.png. This will make it so that we only see the sprites from that image in the list above. Set the batch z order for outside.png to -1 to put all the sprites in outside.png behind everything else.

Next, drag the bgWindow sprite and place it in the empty spaces left when you removed the windows and background sprites.

When you’re ready, select all the new sprites with the window cut (all bgWindow sprites) and set their Z Order to 1.

Add these new sprites to the parallax and set the x component of the movement ratio to 1. Save the level.

Press the Test Level button to run the scene in Scene Tester. You may notice some of the new sprites have “noise.” That’s because some of the positions of the new sprites aren’t lining up correctly with the old sprites.

There are two ways to fix this problem:

  1. Adjust every mismatch individually by selecting each sprite and moving the sprite using the arrow keys until it lines up correctly with the other sprites.
  2. Select all of the misaligned sprites and set Scale X in the General Properties menu to 1.01, or a value that cancels the noise.

Save the level when you’re satisfied with how the sprites are positioned.

Now that everything is in place, let’s create a background that will move at a different speed from the rest of the parallax.

First let’s lock the sprites already in the level so that we don’t accidentally move them. To do this, select all the sprites from the list on the left, and click the Lock button.

Note: To unlock sprites, select them in the list and press the Lock button again.

With the sprites locked, drag the window_background sprite into the scene and place it above the main screen. Make sure that the left vertical border of the window_background sprite is lined up exactly with that of the main screen.

Now make sure the window_background sprite is selected, and duplicate it to the right until the end of the game world by using the green + button (or pressing Command-D). Make sure that you have the correct anchor point selected.

The level should look like this when you’re finished:

Now select all the new window_background sprites and add them to the parallax. Set the x component of the movement ratio to 0.3.

Repeat this process for the window_foreground sprite.

Drag it into the scene and position it above and in alignment with the window_background sprites:

Clone it until the end of the game world:

Add it to the parallax and set the x component of the movement ratio to 0.7:

Now select all the window_background sprites and drag them down onto the main scene so that you can see them through the window.

Repeat the process for the window_foreground sprites. Drag all of the sprites down until you can see them through the window.

Save the level when you’re done.

Running Scene Tester now, you should see the outside world moving at a different speed than the inside world. This makes it seem to the user that they are never seeing quite the same outside image through the window, making the game world feel more three-dimensionally “real.”

The last thing we have to do is add the new level and images to our Xcode project. Drop the new level (“level04”) into the Levels folder in Xcode. Your new Levels folder should look like this:

Then add the new image files (outside.png, outside-hd.png, outside.pshs) to the Images folder. Your new Images folder in Xcode should look like this:

Inside HelloWorldScene.mm, change the loading level code so that it loads the level04 file:

lh = [[LevelHelperLoader alloc] initWithContentOfFile:@"level04"];

There’s nothing left but to run the game and enjoy!

Rocket Mouse final project!

Where to Go From Here?

Congratulations, you’ve finished the game! It’s been a long journey. I hope you enjoyed creating this wonderful game with me, and hardly noticed you were learning in the process. Thanks for reading!

An example project with all of the code from the entire tutorial series can be downloaded here.

As always, I am available to answer questions in the forums for this tutorial series, both here and on the LevelHelper forum.


This is a post by special contributor Bogdan Vladu, an iOS application developer and aspiring game developer living in Bucharest, Romania.

User Comments

69 Comments

[ 1 , 2 , 3 , 4 , 5 ]
  • Im learning a lot from your tutorial but...
    Im running on a iPhone 4S and your tutorial doesn't seem as smoothe as jetpack joyride, is that because of the levelHelp and editor. Would you happen to know what jetpack joyride was made with.
    Would corona be faster than cocos2d, because i like cocos2d much better but i'm open to anything.
    Comradsky
  • Thanks for the nice tutorial.
    I have one more question:
    When I do a restart, an error in the file b2body.h
    on the line: return m_world;
    The method: inline b2World * b2Body :: GetWorld ()
    Can someone help me?
    ProJ0Ra
  • I am using the following code to move the coins like magnet towards the player as CCMoveTo did not work out for me. It works fine for me and gives a brilliant magnetic effect. I have also stored the original position of each coin, but I am unable to reset the coins on the same position when parallax resets (I have tried almost all the ways) . I am curious to implement this. Please help and guide me if I am wrong.

    if(magnetActive)
    {
    for(LHSprite* coin in allCoins)
    {
    float distance = ccpDistance(player.position, coin.position);
    if(distance < 250)
    {
    b2Body *coinBody = [coin body];
    coinBody->SetType(b2_dynamicBody);
    coinBody->SetGravityScale(0);
    coinBody->SetAwake(true);
    coinBody->ApplyForce( b2Vec2(player.position.x-coin.position.x,player.position.y-coin.position.y), coinBody->GetWorldCenter() );
    }
    }
    }

    I want the code to be written in -(void) spriteInParallaxHasReset:(LHSprite*)sprite
    ScottCogniter
  • Thanks for this great tutorial! It really helped me "out of the dark" with game development. Ive just finished it off with success but I got one minor bug with the upper physical border/"ceiling". When the player starts flying and hits it for the first time, everythings ok. But when the player has landed on the floor once and starts flying again, the player turns to the "mouseRun" animation when it hits the ceiling the second time. Its just like when it lands on the floor (without the "mouseFall" anim). The lower physical border is set up with the GROUND tag and works as it should. The upper border has no tag. Is there an easy solution to this? Or have I just missed something?

    Also, are you planning to write an update for this tutorial to work on an iPhone 5? I tried the game in the iPhone 5 simulator but all the physical objects just collapsed together like there was a black hole at the position 0,0.

    Im using Kobold2d, Xcode 4.5.2, the latest Sprite-/Levelhelper and an iPhone 4s as a test device.
    andersmessel
  • This is a great tutorial. Thanks for your great share.

    I just want to ask you something. In jetpack joyride background layers, coins, lasers, boosters everything is regenerating randomly in an endless mode until the player dies. Is it possible to make this at some point of your tutorial? I mean if we want to create an endless runner game, how can we generate those thing randomly?
    decabol
  • mrgenius007
  • hello I have two questions

    1. How would do to raise the speed gradually parallax?

    2. How could put a meter away?

    Thanks for your help
    jaljz
  • how to make fps constant with different devices?

    the problem I have is that when I run the game on a ipadmini are at 60 fps, but when I run it on ipod 5g are between 40-60 fps and this makes the speed of the game is faster and sometimes slower.

    serious question that must be the problem?

    I searched the web and can not find answers.

    thanks for your help
    jaljz
  • This tutorial is really great.


    -----------------------------------
    war commander hack
    Texi
[ 1 , 2 , 3 , 4 , 5 ]

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Vote for Our Next Tutorial!

Every week, we alternate between Gaming and Non-Gaming tutorial votes. This week: Non-Gaming!

    Loading ... Loading ...

Last week's winner: How to Make a Simple 2D Game with Metal.

Suggest a Tutorial - Past Results

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in October: Xcode 6 Tips and Tricks!

Sign Up - October

Our Books

Our Team

Tutorial Team

  • Kyle Richter

... 52 total!

Update Team

... 14 total!

Editorial Team

... 22 total!

Code Team

  • Orta Therox

... 3 total!

Subject Matter Experts

... 4 total!