How To Build a Monkey Jump Game Using Cocos2d 2.X, PhysicsEditor & TexturePacker – Part 1

Andreas Löw

Update 6/26/2013: Updated for Cocos2D 2.X and latest version of Texture Packer and Physics Editor.

In this tutorial, you’ll learn how to make a game about a monkey having a rough day. He’s just minding his own business, but these crazy objects keep falling from the sky!

To see what I mean, check out this cool video:

By the end of this tutorial, you’ll have created a cool physics-enabled game, and learned how using TexturePacker and PhysicsEditor can save you a ton of development time.

To go through this tutorial, you should have some basic familiarity with Cocos2d. If you are new to Cocos2d, check out some of the other tutorials on this site first.

You will also need a copy of TexturePacker and PhysicsEditor. To go through this tutorial you can use an evaluation version from TexturePacker and PhysicsEditor. The trial versions work well with the tutorial.

Ready to make that monkey jump? Keep reading to get started!

Designing the game

Before diving into code, it’s best to think about the design of the game first. Let me give you a brief overview of how the game will work.

As you’ve seen, your main character will be a monkey. You’ll control his movement with the accelerometer – that is, by tilting the iPhone or iPad left and right. The monkey will jump when the screen is tapped.

During the game, items will drop from the top of the screen, and the dropping frequency will increase the longer the game continues. You will have a drop indicator to show the player where the next item will fall. Items will pile up, and the monkey must stay on top to stay alive.

There will be two sorts of items: those that hurt the monkey, and those that heal him – namely, bananas! The monkey’s health will be displayed in the top-left corner as a bar of bananas.

The score will be displayed in the top-right corner, and will be at the height of the highest item on the stack.

Handling iPhone 5

When this tutorial was first written, iPhone 5 was not available yet, so the tutorial only had to support a single aspect ratio (3:2, or 960×640 pixels on retina devices).

With iPhone 5 you have a new wider aspect ration to deal with (16:9). It has a resolution of 1136×640 pixels – which means that you have to fill an additional 176 pixels in the width.

If you were to use the artwork from the original tutorial without any changes, you would have a black bar on the right side of the screen – which is obviously ugly.

One way to deal with the problem is to scale the scene to fit the width.

Scaling up from iPhone 4 with 960×640 results in a virtual screen size of 1136×757 pixels – which is 117 pixels that are not visible on iPhone 5. This is a good solution in many cases.

But it would change the gameplay for MonkeyJump – making it harder for iPhone 5 players since they have less space on the screen.

That’s not fair!

In this tutorial, you are going to do a mixture of both. You’ll use the same sprites and playfield size as on iPhone 4 – but you will scale the background to fit and center the playfield.

The result already looks promising, but not perfect. The objects would pile up near an invisible border. That might be nice for a science fiction game with force fields, but not for the jungle setting.

To fix the issue, you’ll add some leaves as a border on each side. They’ll be in the foreground – occluding a bit of the game field – building the barrier to the left and right.

You’ll make them scroll slower when the monkey jumps up. This adds some additional depth to the game and gives a nice visual effect.

This is how the final game screen will look on iPhone 5. You are going to add the additional space only to iPhone 5 – the “old” devices are left without additional borders.

Getting Started

To get started, download the tutorial source code and unzip it into your preferred location. Your starting point will be 0-BaseProject, which contains the basic Cocos2d setup and some integrated assets.

I’ve added directories for several stages of the development process containing the results you should achieve. These directories will make your life easier in case you get lost, or if you simply want to skip one part of the project.

Each folder contains the complete project and is independent from the rest. Inside each folder are two more folders:

The first is called Assets, and contains such things as the TexturePacker and PhysicsEditor save files, a directory holding the Xcode project, and a free game art pack created by Vicki Wenderlich.

The Assets folder is organized further into the subfolders background, containing the background graphics, and jungle, containing the foreground objects and animation.

The second folder is called MonkeyJump. It contains the Xcode project and the sources, which are contained in a second MonkeyJump folder which has the following subfolders:

  • libs: Cocos2d, Box2d, Cocos Denshion… all the Cocos2d stuff
  • GBox2D: my ObjectiveC++ Box2d wrapper
  • Resources: sounds and music

Creating Sprite Sheets

Time to start the real work, beginning with sprite sheets.

Open the 0-BaseProject folder and have a look at the Assets folder. I’ve already arranged the images for you. You’re going to add the sprites from the background folder into one sprite sheet, and the sprites from the jungle folder into as second.

The background sheet will use RGB565, a 16-bit image format that reduces the amount of memory used by 50%. Using RGB565 will also speed up rendering. The foreground sheet, with most of the content, will use RGBA8888 for full quality. For more information on pixel formats with TexturePacker, check out this tutorial.

Creating the Background Sprite Sheet

Let’s start by creating the background sheet. Start TexturePacker, navigate to the O-BaseProject\Assets\background folder in Finder, and drag it into the right-hand side of the TexturePacker window. It will be blank at first, but don’t worry – that’s just because this is a big image.

To get it to show up, set the Max Size W and H values to 4096. Now that the sprite sheet is big enough, you’ll see the image appear.

Below is a screenshot of what you’ll see and the settings you’ll be changing. You can either change the settings by following the screenshot, or keep reading and I’ll walk you step-by-step through the process, along with an explanation of what each setting does.

To set up the parameters for the texture, first set the Data Format to Cocos2d. Next set the Texture format to .pvr.ccz. The main advantage of pvr.ccz is that it can be loaded quite fast, and it usually consumes less memory than PNG.

You may see a warning sign at this point next to Premultiply alpha. Move your mouse over that, and select Enable premultiply alpha to suppress the warning.

PremultiplyAlpha

Pre-multiply means that all color values get multiplied by their transparency value when saving the file. This speeds up graphics rendering in the game, since the multiplication step does not need to be performed at runtime.

Next, change the Image format to RGB565. This means that you are using only 16 bits to store color information, down from the default RGBA888 (32 bits). This results in some incredible memory savings – if you look in the lower right of Texture Packer, you’ll see it’s gone down from 32768KB to 16384KB!

You might have noticed by doing this, the quality of the image has suffered a bit. You can see that the gradients are now not as smooth and have some artifacts called banding. You can compensate for this by setting Dithering to FloydSteinberg.

The following two images show a part of the jungle. The left one has no dithering, while on the right one, dithering has been enabled:

RGB565 image with banding artifacts

RGB565 image with dithering

Set Size constraints to Any Size. First generation iPhones required textures to be a power of 2, but newer devices all can handle non-power-of-2 sizes. Also check Force word aligned.

Word aligned means that the number of bytes per row can be divided by 4. If you have an odd width like 1011px (that is 1011 pixels/row * 2 bytes/pixel = 2022 bytes/row, which isn’t divisible by 4), TexturePacker will extend the sheet to make it 1102px wide – that makes 551 words.

This is important to set because 16-bit textures like the background require it. If you forget to set this, Cocos2d will warn you and instruct you to play with the width of the sprite sheet until it’s an even number.

Next, set the Data file to background-568h@2x.plist in the Resources directory of the Xcode project. This will automatically set the Texture file to background-568h@2x.pvr.ccz.

Don’t worry that the paths are displayed as absolute paths. TexturePacker creates a relative path to the document file as soon as it is saved. This means you can move your project around as long as you don’t change the relative position of the assets and resources to the saved .tps file.

The -568h@2x extension is important, because TexturePacker can save retina display and reduced-resolution images for older devices. To enable this, simply click on the cog wheel in the AutoSD option.

An overlay pops up:

In most cases using one of the presets from the top of the dialog is sufficient. But in this case you need some detailed control.

Set the Main extension to -586h@2x.. This part of the data and image file name will be replaced with a different extension for the scaled variants.

Press the “+” button to add a scaling variant for retina display on iPhone 4.

Set Scale to 0.84507 – that’s simply 960 divided by 1136 – scaling the iPhone 5 background down to iPhone 4′s width. Set the Extension for this variant to -hd. (don’t forget the “.”).
Use an H value of 4096.

Press the “+” button again to add another scaling variant for non-retina displays.

Set Scale to 0.422535 – that’s simply 480 divided by 1136 – scaling the iPhone 5 background down to iPhone 3GS’s width. Set the Extension for this variant to . (yes – a single “.”).

Finally, click Save and save your Texture Packer file inside your project’s Assets folder, named background.tps. Then Publish. This creates six files in the Resources folder: background-hd-568h@2x.plist, background-568h@2x.pvr.ccz, background-hd.plist, background-hd.pvr.ccz, background.plist and background.pvr.ccz.

Open the Resources folder to make sure the files are there. You should see something like the following:

Background sprite sheets

Creating the Jungle Sprite Sheet

Now create a new sheet for the foreground by pressing the New button on the toolbar. Drag the complete jungle folder from the Assets folder into the empty right pane.

See how TexturePacker builds the complete directory structure on the right pane? You can expand the tree view by pressing the arrow button to the left of the word “jungle”.

TexturePacker

You are using a feature called Smart Folders, where TexturePacker rescans the directory every time you add sprites to the folder. If you want to add a new asset, or rename or change an existing asset, just drop it into the folder on your file system, and when you re-enter TexturePacker it will automatically update the sheet.

Yellow folders are the main folders you added, blue ones are folders found in the directory structure.

Note: If you need to update many sprite sheets at once, you can switch to Terminal and update all .tps files from the command line by calling TexturePacker *.tps.

You can even integrate TexturePacker with your Xcode build process. This has several advantages:

  1. You no longer need to worry about sprite sheets – just add the sprites to your Assets folder, and you’re done.
  2. If you use version control, sprite sheets will hang around as binary blobs. This will grow your working copy size pretty fast, especially when using Git or Mercurial for version control. The easy way around this is to simply not add the sprite sheets to version control, and instead create them on the fly.

For more information on integrating TexturePacker with Xcode 4, check out this tutorial.

For the options, you’ll use a nearly identical setup as for the background sheet (see screenshot above). Just make sure to leave the image format at RGBA8888 for full quality.

In AutoSD simply select cocos2d hd/sd and press Apply. This sets the 0.5 scaling for retina and non-retina display images.

Also be sure to leave the padding at the default of 2. This will reduce artifacts on the sprite borders. Sometimes OpenGL drags in pixels from neighboring sprites. Padding avoids this problem by adding transparent borders around the sprites.

Note: If you want to use sprites as tiles with seamless connections, you must use either select extrude or reduce border artifacts, which replaces the transparent borders with colored ones. If you do not do this, you might get transparent lines between the tiles.

Set the Data File to jungle-hd.plist in the Resources folder of your Xcode project. This will automatically set the Texture File to jungle-hd.pvr.ccz.

Finally, save the sheet as jungle.tps in the Assets folder and click Publish. This creates 4 files in the Resources folder: jungle-hd.plist, jungle-hd.pvr.ccz, jungle.plist, and jungle.pvr.ccz. Check and see if they are there.

Creating the Frame Spritesheet for iPhone5

You need one more sprite sheet – this one for iPhone 5, to contain the tree left and right which occupies the additional space.

You can’t simply add the tree to the jungle sprite sheet since the tree is more than 2048 pixels high. iPod 4G only allows a maximum texture size of 2048×2048. You could split the tree into 2 parts and join it by using sprites on the device – or create another sprite sheet which is only loaded on iPhone 5. I prefer the latter since it makes things easier and avoids loading additional bytes on most of the devices.

You’ll be using almost the same settings as for the jungle sprite sheet. To avoid re-configuring all the settings, you can set the settings you currently have in the jungle sprite sheet as your default settings.

To do this, click the Save defaults option in the toolbar while the jungle is opened. Then open a new sprite sheet, and it will contain the settings you just saved as defaults. Open AutoSD and clear Main extension and press the “-” button on all list items.

Then drag in the frame folder instead of the jungle folder, and make sure the Max Size W and H are set to 4096.

Save the file as frame.plist and frame.pvr.ccz.

Creating Physics Shapes Manually

Next you’ll set up the physics shapes with PhysicsEditor.

Launch PhysicsEditor, and start by setting the Exporter to Box2d Generic (PLIST). I made an Objective-C loader for this format that you can use in your Cocos2d projects. If you are not satisfied with the formats supported by PhysicsEditor, you can simply create your own custom data exporter.

Next set the PTM-Ratio to 170. This tells Box2d that 170 pixels are equal to 1 meter (39.37 inches), the internal measurement unit of Box2d. I chose 170 pixels because this is the monkey’s height, and he is going to be 1 meter tall in the simulation.

You’re going to create all the physics shapes for the high-res sprites only. This is okay, since you want the physics parameters to be identical on retina display and older displays.

Creating different shapes for different screen resolutions would create different masses for each object, resulting in different physics behavior – which you don’t want! The idea is to run the same simulation on all devices and only adjust the visuals.

Setting Up the Fixture

You’re now going to create your first shape by hand. Drag the jungle/floor/grassfront.png shape onto the left pane of PhysicsEditor. When you do this, the shape will also appear in the center pane, which is the main editor area.

Note the small blue circle with the cross inside: that is the anchor point of the shape. You’re going to leave the anchor point for this shape where it is, on the bottom left corner. Later on, I’ll show you how to change the anchor point.

First use the polygon tool from the top tool bar. As soon as you click on it, a small red triangle appears. You can drag this triangle with your mouse.

The handles allow you to change the triangle’s shape. Double-clicking somewhere near a line will add an additional vertex. Double-click the vertex to remove it.

You do not have to care about things like polygon orientation or convex or concave shapes. This is all handled by PhysicsEditor.

Now create a rectangular shape for the floor. It should cover the soil but not the grass, and look something like this:

Setting Up the Parameters

Since the floor is going to be a static shape, its parameters are going to be very basic.

Density affects the weight of a body/fixture. Since the floor will not move, the value of the density is of no importance and you can leave it at the default value (2.0). Restitution is the degree a colliding shape will rebound from another object. For the floor, you want to keep this at 0.

Friction is the only important value here, as it determines how much the sprites will slide on the floor. Set this to 0.7.

The next items to set up are the collision bits. PhysicsEditor lets you handle these parameters quite easily – that is, without doing hex math. First, give the bits useful names by setting up the names in the text fields associated with the bits:

  • floor – both the floor and the walls
  • monkey – what else?
  • good_objects – objects that don’t hurt the monkey
  • bad_objects – objects that hurt the monkey

Ignore the other bits – you don’t need them.

Cat. stands for Category – it describes the type of an object. An object can be part of multiple categories at once. Mask describes which objects it can collide with.

Two objects can collide only when Object B’s bit category is of a type that has been selected as a mask for Object A, and vice-versa. Tick the Category field for the floor bit since this shape is a floor, and then the Mask field for all the bits. This enables collision between all objects and the floor.

Save the project as shapes.pes inside the Assets folder.

A Little Magic: Creating Shapes with the Tracer

Creating shapes by hand isn’t always fun. So let’s use the magic wand tool and let PhysicsEditor do the work for you!

Drop the following objects onto the left pane: backpack, banana, bananabunch, canteen, hat, pineapple, and statue.

Select the backpack, and click the magic wand icon on the tool bar. This will open a new window, the tracer. The tracer shows the current shape with an overlay of the traced shape.

The most important setting is the Tolerance. This value tells the tracer how exactly the polygon should match the shape. It directly influences the number of vertices the polygon will have. For example, setting the Tolerance to 20 will result in a 5-vertex polygon that no longer fits the shape; setting it to 1 creates a perfectly-matching polygon with 39 vertices.

Not enough vertexes

Too many vertexes

Good trace

Set the Tolerance to 5, which gives you an acceptable match with about 15 vertices (the exact amount sometimes varies with the starting point of the tracer). That’s fine! Click OK to get back to the main screen. If you want you can fine-tune the shape, but don’t be too finicky!

Now set the backpack’s parameters. Set the Density to 5.00, the Restitution to 0.10 (so that the backpack bounces a little bit) and the Friction to 0.5. You might want to play around with these parameters later to get a better feel for how they work.

In the collisions section, tick the Cat. box next to bad_objects and make sure no other categories are selected. Also enable collision with all other objects by making sure all the Mask boxes are ticked.

Drag the anchor point (the blue circle with the cross inside) to the center of the backpack shape. Alternatively, you can set the anchor point to the exact center of the shape by setting the Relative anchor point values in the right sidebar to 0.5 and 0.5.

Now that you know how to create shapes, either by hand or using the tracer, go ahead and do the remaining ones yourself! Use the parameters indicted in the table below. And don’t forget to drag the anchor point for each shape to the position indicated in the images below.

Object Tolerance Density Restitution Friction Category (Cat.) Mask
backpack 5 5.0 0.1 0.5 bad_objects floor, monkey, bad_objects, good_objects
canteen 5 3.0 0.1 0.2 bad_objects floor, monkey, bad_objects, good_objects
hat 5 1.0 0.15 0.4 bad_objects floor, monkey, bad_objects, good_objects
statue 5 10.0 0.05 0.7 bad_objects floor, monkey, bad_objects, good_objects
pineapple 5 1.0 0.4 0.7 bad_objects floor, monkey, bad_objects, good_objects
banana 5 1.0 0.5 0.5 good_objects floor, monkey, bad_objects, good_objects
bananabunch 5 1.0 0.3 0.7 good_objects floor, monkey, bad_objects, good_objects

Last But Not Least: the Monkey

There’s one shape you have left to create, and that’s because it’s the most complex. Your monkey consists of quite a few animation phases (fourteen total):

The first idea that comes to mind is to add a collision polygon for each animation frame to make a perfect match for each phase of the animation. But this would be a bad idea.

First of all, the frequent exchange of fixtures would consume a good deal of CPU power. But the bigger problem is that with each new shape, the mass of the monkey would change!

This is because Box2d calculates mass based on a polygon’s area and density. If you change the polygon, the monkey will gain or lose weight! And this will lead to the physics simulation behaving erratically.

The solution is to make only one shape for the monkey, which you will try to make as good a fit as possible. To begin, drag one of the monkey shapes onto PhysicsEditor (for example, walk\right_1.png will do fine). Then double click the shape in Physics Editor, and rename it to monkey.

Now click the “+” button shown in the screenshot above and add the other images for the monkey (you can just select all the pngs in the monkey folder). The files will show up in the combo box and you can switch between them to look at them at a glance.

Start the tracer by clicking on the magic wand icon. What you see now is that the shape does not fit the monkey anymore. This is exactly what you want.

The tracer is now in animation tracing mode. You can switch between two frame modes: intersection and union. Intersection creates the polygon only from the parts that are covered by all sprites, whereas union uses the parts that are covered by any sprite. Choose intersection.

The slider allows you to browse through the animation phases and see how the shape fits the different phases.

If you want, you can add one of these shapes to the monkey as a temporary estimate. This will help you make your own shapes that fit the monkey’s animation frames. Or you can just click Cancel to dismiss the dialog and build it yourself – it’s up to you.

Here is what you have to build:

Sometimes polygon shapes don’t slide smoothly over other polygons and stick to each other. This is why you’re building the monkey from circle shapes.

The other reason is that using circles results in faster collision detection. The math to check if a point is inside a circle is much simpler than that for testing for a point inside a polygon. So you’ll save CPU power. You’ll delete the tracer shape after the other parts are set up.

First build the head of the monkey. Use a circle shape and make it as big as the traced version. The head must be treated differently from the other body parts, since you want the monkey to get hurt when objects land on his head from above.

To distinguish the head from the other parts, set the Id to head. Set the head’s category to monkey, and make sure it collides with good_objects, bad_objects, and floor. Set the density to 2.0, the restitution to 0.1, and the friction to 0.5.

Adding head to monkey

Next build the body of the monkey from two circles, one covering the torso and one the legs. For both, set the Id to body. Set the body’s category to monkey, and make sure it collides with good_objects, bad_objects and floor. Set the body’s density to 2.0, its restitution to 0.1, and its friction to 0.5.

Cycle through the different animation phases by selecting the different images from the Filename combo box. Check if the circles fit all phases. It doesn’t have to be a perfect fit, and you can come back to PhysicsEditor any time you want to tweak the collision set.

If you added the polygon from the tracer earlier, delete the polygon created with the tracer now.

During the game you need to check if there are objects to the left or right to trigger the push animation. To do this, place two sensors on the left and right sides of the monkey.

Sensors are fixtures that don’t interact with other bodies and simply report collisions. So to make a fixture a sensor, select each sensor shape and check IsSensor.

Set the Id to push_left for the left sensor and push_right for the right one. For each shape, set the category to monkey, and make sure it collides with bad_objects only – you don’t want the monkey push the floor and the bananas away!

Make the monkey shape fit all animation phases

One last thing to do: save and click Publish! When asked for a location, store the file in the Resources folder of the project as shapes.plist.

That’s it for now in PhysicsEditor. Next you move to Xcode and start coding your game!

The Xcode Project: Overview

Next, open up the Xcode project that you downloaded earlier from the tutorial source code (0-BaseProject\MonkeyJump\MonkeyJump.xcodeproj).

I created this starter project using the standard Cocos2d+Box2d template that comes with Cocos2d and removing all the demo stuff. I also added the sound resources and some classes which will make your life easier when working with Box2d inside Cocos2d.

One important thing to know about this project is that all files must have the .mm extension instead of .m. This is because Box2d is C++ based, so you must use Objective-C++ for development.

Note: If you get strange errors during compilation – like MonkeyJump/libs/Box2D/Common/b2Settings.h:22:10: fatal error: 'cassert' file not found – this is because a file including the b2Settings.h still has the .m extension. It can’t find the C++ header files required by Box2d because the compiler assumes that it’s a C file. Changing the extension to .mm fixes the issue.

Now let’s discuss the starter project in more detail:

GBox2D

First let me give you some information about GBox2D. GBox2D is an Objective-C wrapper for Box2d that I developed for my own games.

These are the main classes in Gbox2D:

  • GB2Engine: This class wraps the Box2d world simulation. It runs the simulation, updates all sprite positions and rotations and can iterate through all objects in the world. It is implemented as a singleton class to make access as easy as possible.
  • GB2Node: This class combines a CCNode and a B2Body. It’s the glue between the physics simulation and the graphics representation inside Cocos2d. It also contains selectors for simple management of the physics object and implements a proxy to access the inner node’s data.
  • GB2Sprite: This class is derived from GB2Node and specializes in using CCSprite as the inner object.
  • GB2DebugDrawLayer: This is a Cocos2d layer that wraps the debug drawing. It can be added to your project like a normal layer. When added, it will draw the physics shape outlines. The nice thing about it is that it detects when running on a retina display target and scales the content accordingly.
  • GB2Contact: This structure will be passed as parameter to an object when a collision is detected. Both objects involved in the collision will be called for each single point of contact.
  • GB2WorldContactListener: This is a C++ class that reacts to collisions in the physics simulation.

If you’re curious, go ahead and scan through these classes to get an idea of what they do. Don’t worry if you don’t understand what they’re doing – you’ll learn how to use them it in the next few sections.

Collision Detection with Gbox2D

GBox2D makes collision detection a piece of cake! This is because you do not need to create one huge switch-case statement or a series of if-else cascades to detect the various possible collision combinations.

Instead, GBox2D simply uses the names of the colliding classes and calls selectors with names derived from the class names! If this sounds too abstract, take a look at the following example:

Let’s assume you have a monkey that is an object of class Monkey, and a banana that is an object of class Banana. If both objects begin to collide, the following selectors will be called by GBox2D:

    [banana beginContactWithMonkey:collisionA];
    [monkey beginContactWithBanana:collisionB];

If the collision is released, because the objects do not touch anymore:

    [banana endContactWithMonkey:collisionA];
    [monkey endContactWithBanana:collisionB];

The collisionA and collisionB parameters contain collision information, e.g., which objects and which fixtures took part in the collision. You will use this information to see if the monkey was hit on his head or body.

AppDelegate

Things I changed from the AppDelegate contained in the original Box2d project are as follows:

First I set the default pixel format of the frame buffer to RGBA8. That means that the game gets the full 24-bit color depth. I disabled the depth buffer, since you don’t need it.

	CCGLView *glView = [CCGLView viewWithFrame:[window_ bounds]
							pixelFormat:kEAGLColorFormatRGBA8
							depthFormat:0	//GL_DEPTH_COMPONENT24_OES
							preserveBackbuffer:NO
							sharegroup:nil
							multiSampling:NO
							numberOfSamples:0];

The next important thing to set is pre-multiplied alpha. This is because you use pre-multiplied PVR images created with TexturePacker. If you don’t set pre-multiplied alpha, your images will have dark borders.

    [CCTexture2D PVRImagesHavePremultipliedAlpha:YES];

Since you’re going to use the random generator to select objects to drop, you have to seed it. The best way to do this is using time(). If you forget to seed the random generator, you will still get random numbers – but they will be the same with every start of the game.

    srand (time (NULL));

When the initialization is done, the GameLayer scene is started:

    [director_ pushScene: [GameLayer scene]];

GameLayer

The GameLayer is a simple class derived from CCLayer. In this state it consists of an empty init function…

    -(id) init
    {
    	if( (self=[super init]))
        {
    	}
    	return self;
    }

…and a static selector that wraps the CCLayer into a CCScene to hand over to the Director:

    +(CCScene *) scene
    {
    	// 'scene' is an autorelease object.
    	CCScene *scene = [CCScene node];
 
    	// 'layer' is an autorelease object.
    	GameLayer *layer = [GameLayer node];
 
    	// add layer as a child to scene
    	[scene addChild: layer];
 
    	// return the scene
    	return scene;
    }

Audio Resources

I got the theme music, tafi-maradi-loop.caf, from http://incompetech.com/

Since I knew I wanted to loop the music, I made things easier on myself and chose a theme without a vocal track.

The sound effects for the objects were obtained from http://soundbible.com (thanks to Mike Koenig). Some of them were created using cfxr.

All sounds and the music files were converted to .caf format. See Ray’s Audio 101 tutorial for more information.

If you compile and run the project, you will simply see a black screen. So let’s add some content to that screen now!

Basic Setup in Xcode

The goals for this section of the tutorial are to set up the basic game layers and backgrounds, and to set up the physics engine.

If you’ve followed along the tutorial so far you should have the same setup as in the 1-ReadyToCode folder.

Before you dive into the code, there’s one more thing to do: add the resources you created to XCode. Remember these?

  • background.plist
  • background.pvr.ccz
  • background-hd.plist
  • background-hd.pvr.ccz
  • background-568h@2x.plist
  • background-568h@2x.pvr.ccz
  • frame.plist
  • frame.pvr.ccz
  • jungle.plist
  • jungle.pvr.ccz
  • jungle-hd.plist
  • jungle-hd.pvr.ccz
  • shapes.plist

Add the above files to the Resources folder in your Xcode project by Control-clicking on the Resources folder inside Xcode, selecting Add Files To “MonekyJump” and then selecting the files listed above from the project Resources folder.

Now for the code. First, you need to create a “Floor” class to represent the floor for the game. Add Floor.h and Floor.mm files to your project by creating a new file with the iOS\Cocoa Touch\Objective-C class template. Name the class Floor, and make it a subclass of GB2Sprite. And don’t forget to change the extension for Floor.m to .mm once it has been created.

Floor.h should simply contain this:

#pragma once
 
#import "cocos2d.h"
#import "GB2Sprite.h"
 
@interface Floor : GB2Sprite
{
}
 
+(Floor*) floorSprite;
 
@end

And Floor.mm this:

#import "Floor.h"
 
@implementation Floor
 
+(Floor*) floorSprite
{
    return [[[self alloc] initWithStaticBody:@"grassfront" spriteFrameName:@"floor/grassfront.png"] autorelease];
}
 
@end

The only remarkable line of code here is the call to the initWithStaticBody selector. This makes your object a static object – one that isn’t moved by the physics engine. It initializes the body’s shape using a shape from the shapes.plist file with the name grassfront.

It also uses a sprite image with the name floor/grassfront.png that is taken from jungle.plist.

Why did you derive this class from GB2Sprite instead of simply using a GB2Sprite directly? The answer is GBox2D’s collision handling, which uses the name of the class to call appropriate selectors on the colliding objects. Since you want to know when something collides with the floor, the class name for the floor object must be distinguishable from other GB2Sprite objects.

The next thing to do is update the GameLayer. Add some instance variables to hold the required objects in GameLayer.h:

@interface GameLayer : CCLayer
{
    CCSprite *background;                   //!< weak reference
    CCSprite *floorBackground;              //!< weak reference 
    CCSpriteBatchNode* objectLayer;         //!< weak reference
 
    CCSprite *leftFrame;                    //!< weak reference
    CCSprite *rightFrame;                   //!< weak reference
}

You will store the objects as weak references – that is, without increasing the retain count for each object.

You do not need to worry about the CCSprites being deleted. They will be added as children of the CCLayer and thus have a retain count of at least 1. This is done because otherwise you will be caught in a retain cycle and not able to free the memory allocated by these objects.

Now fill GameLayer.mm‘s init selector with content. First, load the jungle sprite sheet:

[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"jungle.plist"];

For the background you have to check if the game runs on iPhone 5 and load the appropriate sheet. One way to detect this is to use the window size. The advantage of using the size is that if some other device with the same width or height comes out, the game might still adapt to that one too.

The director delivers the size in points – which is 1 pixel in on a non-retina display and 2 pixels on a retina display. Thus checking for the size has to be done by checking against 568 (1136/2).

You also need to check against height and width because the orientation changes after startup phase and width and height are swapped.
This is also a good point to calculate the offset from the center of the screen you have to take into account for iPhone 5. The distance is (1136-960)/2 in pixels or (568-480)/2 in points. The offset for non-iPhone 5 devices is 0.

To center the game screen on the device simply set the offset value for the x coordinate.

CGSize winSize = [CCDirector sharedDirector].winSize;
bool is5 = (winSize.height == 568) || (winSize.width == 568);
CGFloat centerOffsetX = is5 ? ((568-480)/2) : 0;
self.position = ccp(centerOffsetX,0);

Now load the background depending on the device:

if(is5)
{
    // load the extended background from iPhone5
    [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"background-568h@2x.plist"];
}
else
{
    // load the standard background
    [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"background.plist"];
}

Next, load the physics shapes into the GB2ShapeCache:

[[GB2ShapeCache sharedShapeCache] addShapesWithFile:@"shapes.plist"];

Then, set up the layers. You’ll divide the game into the following layers:

  • Background layer: The jungle image
  • Floor background: A single sprite with the tall grass
  • Object layer: Contains all the items and the monkey
  • Borders: (iPhone 5 only) Restrict the playfield
  • Debug draw layer: Activated as needed
  • Hud layer: Has the score and life energy indicators (to be added later)

The background layer is a child of the playfield and would now leave a gap of 44 points to the left on iPhone 5. You have to move it back to the left by the centerOffsetX to center it again.
On other devices centerOffsetX is 0 – so everything is fine here.

Add the code to create the basic background and floor background layers to init.

// Setup background layer
background = [CCSprite spriteWithSpriteFrameName:@"jungle.png"];
[self addChild:background z:0];
background.anchorPoint = ccp(0,0);
background.position = ccp(-centerOffsetX,0);
 
// Setup floor background
floorBackground = [CCSprite spriteWithSpriteFrameName:@"floor/grassbehind.png"];
[self addChild:floorBackground z:1];
floorBackground.anchorPoint = ccp(0,0);
floorBackground.position = ccp(0,0);

Then, add the object layer. This will be a sprite batch node to speed up rendering of the objects:

objectLayer = [CCSpriteBatchNode batchNodeWithFile:@"jungle.pvr.ccz" capacity:150];
[self addChild:objectLayer z:10];

And finally, the debug draw layer:

// add the debug draw layer, uncomment this if something strange happens ;)
[self addChild:[[GB2DebugDrawLayer alloc] init] z:30];

If you want to disable the debug drawing, simply comment out the second line. If enabled, the physics shapes will be drawn over the sprites, allowing you to see where collisions happen and if all shapes are properly aligned.

Next, add the floor object as the child of the object layer. Include Floor.h at the top of GameLayer.mm:

#import "Floor.h"

Then add the floor object at the end of init:

[objectLayer addChild:[[Floor floorSprite] ccNode] z:20];

There isn’t anything more you have to do to add the objects to the physics world – everything else is covered inside Gbox2D!

One final step is required for iPhone 5: Loading and adding the left and right frame objects. They have to be placed outside of the play field:

if(is5)
{
    [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"frame.plist"];
 
    leftFrame = [CCSprite spriteWithSpriteFrameName:@"left.png"];
    [self addChild:leftFrame z:20];
    leftFrame.anchorPoint = ccp(0,0);
    leftFrame.position = ccp(-44,0);
 
    rightFrame = [CCSprite spriteWithSpriteFrameName:@"right.png"];
    [self addChild:rightFrame z:20];
    rightFrame.anchorPoint = ccp(1.0,0);
    rightFrame.position = ccp(568-44,0);
}

Build and run the project in the iPhone Retina 4-inch simulator. You should see something similar to this:

Nice – now let’s add some action to the game!

Dropping Objects

You have two related goals for this section of the tutorial: make your objects drop from the sky and add your sound effects.

Our base class for all the dropping objects will be called “Object”. It will handle the sound and some basic collision detection. You will derive other subclasses later on in the tutorial from the Object class.

First, create a new file with the iOS\Cocoa Touch\Objective-C class template. Name the class Object, and make it a subclass of GB2Sprite. (And remember to change the extension for Object.m to .mm)

Object is a simple class, derived from GB2Sprite. This means that it comes with physics and graphical capabilities built in.

To make your life easier, I’ve named the sound files in the same way as the physics sprites and the images from the sprite sheet. This allows you to simply use the object’s name to create the right shape and sound when needed. You’re welcome!

In order for this to work, you need a property named objNameobjName is passed into the initWithObject selector and stored as part of the class.

RandomObject is a factory method that creates a random object and hands over the right object name upon creation.

Paste this code into Object.h:

#pragma once
 
#import "cocos2d.h"
#import "GB2Sprite.h"
 
@interface Object : GB2Sprite
{
    NSString *objName; // type of the object
}
 
@property (retain, nonatomic) NSString *objName;
 
-(id) initWithObject:(NSString*)objName;
+(Object*) randomObject;
 
@end

Let’s now go to Object.mm. Start with some needed imports and with synthesizing the objName property.

#import "Object.h"
#import "GB2Contact.h"
#import "SimpleAudioEngine.h"
#import "GMath.h"
 
@implementation Object
 
@synthesize objName;

GMath.h contains some helper functions – for example, gFloatRand, a ranged floating-point random number generation.

Next, add the init selector and instantiate the physics object. You can use the object’s name as it is to instantiate the physics shape. For the sprite frame name, you’ll need to add the folder’s name (which is object) and the .png extension. Store the objName in the property – you’ll need it during collision detection to play the sound effect.

-(id) initWithObject:(NSString*)theObjName
{
    self = [super initWithDynamicBody:theObjName
              spriteFrameName:[NSString stringWithFormat:@"objects/%@.png", theObjName]];
    if(self)
    {
        self.objName = theObjName;
    }
    return self;
}

In the dealloc selector, simply release the objName property and call super dealloc:

-(void) dealloc
{
    [objName release];
    [super dealloc];
}

The next thing to add is your static factory method, which will simply create a random object. I decided to use a simple switch-case statement for this. The reason is that you’ll need to create special classes for banana and banana bunch later on. These two objects get only one case entry, while the other objects get three each, so that there’s a higher probability they appear more often.

Switch-case constructs are quite efficient (usually implemented by the compiler using a jump table). You might save some CPU cycles by using an array with the names instead, but since the routine will be called once in a second, your way is fine.

+(Object*) randomObject
{
    NSString *objName;
    switch(rand() % 18)
    {
        case 0:
            objName = @"banana";
            break;
 
        case 1:
            objName = @"bananabunch";
            break;
 
        case 2: case 3: case 4: case 5:
            objName = @"backpack";
            break;
 
        case 6: case 7: case 8:
            objName = @"canteen";
            break;
 
        case 9: case 10: case 11:
            objName = @"hat";
            break;
 
        case 12: case 13: case 14:
            objName = @"statue";
            break;
 
        default:
            objName = @"pineapple";
            break;
    }
    return [[[self alloc] initWithObject:objName] autorelease];
}

Finally, add the closing end to the file:

@end

Now switch to GameLayer.h and add a forward declaration for the object class, directly after the #import statement:

#import "cocos2d.h"
@class Object;

Add these new members to the GameLayer class:

ccTime nextDrop;    // Will keep the time until the next drop.
ccTime dropDelay;     // The delay between two drops.
Object *nextObject;   // Contains a reference to the next item to drop.

Switch to GameLayer.mm and add an import of Object.h to the imports at the start of the file. Also import GMath.h:

#import "Object.h"
#import "GMath.h"

Initialize the new variables at the end of the init selector, and schedule an update selector with every frame update:

nextDrop = 3.0f;  // drop first object after 3s
dropDelay = 2.0f; // drop next object after 1s
 
[self scheduleUpdate];

The last line will call a selector called “update” for every frame. The parameter to this selector is the time elapsed since the selector was last called. Add the update method right after init:

-(void) update: (ccTime) dt
{
    // 1 - drop next item
    nextDrop -= dt;
    if(nextDrop <= 0)
    {
        // 2 - do you have the next object?
        if(nextObject)
        {
            // 3 - set the object as active, making it drop
            [nextObject setActive:YES];
 
            // 4 - set next drop time
            nextDrop = dropDelay;
            // reduce delay to the drop after this
            // this will increase game difficulty
            dropDelay *= 0.98f;            
 
        }
 
        // 5 - create new random object
        nextObject = [Object randomObject];
        // but keep it disabled
        [nextObject setActive:NO];
 
        // 6 - set random position
        float xPos = gFloatRand(40,440);
        float yPos = 400;
        [nextObject setPhysicsPosition:b2Vec2FromCC(xPos, yPos)];
 
        // 7 - add it to your object layer
        [objectLayer addChild:[nextObject ccNode]];
    }
}

Let’s go through the above code section by section.

  1. This section simply reduces the time interval since update was last called from nextDrop. If nextDrop falls below 0, it’s time to create a new item to drop.
  2. If the nextDrop timer runs out, this section checks if there is already an object stored in nextObject.
  3. If so, it’s set to active in here. Setting the object to active gives the physics engine control over the object.
  4. This section sets the time until the next drop to the current drop delay, and reduces the drop delay by 2%, making the game a bit more difficult with each dropped item.
  5. This section creates a new object to drop using your factory method in Object – randomObject – and sets the object to inactive, which keeps the object from dropping and participating in the physics simulation.
  6. This section gives the object a random position. The playfield is 480pt wide. The code ensures that the object’s position is somewhere between 40 and 440 points. The section also sets the y-coordinate to 400 for the starting position so that the object will start offscreen from the top of the screen. The b2Vec2FromCC method is used to create a box2db2Vec2 from the point coordinates. B2Vec2FromCC transforms Cocos2d’s points to Box2d’s meters-based values.
  7. Finally, this section adds the object to the object layer.

Compile and run! You should see something similar to the following but of course, with different items. The items look a bit blurry since debug drawing is still enabled:

Disable the debug draw layer as follows by commenting out the relevant line in GameLayer.mm:

    //        [self addChild:[[GB2DebugDrawLayer alloc] init] z:30];

Now your game should look much nicer:

Notice how the items can tumble out-of-screen to the left and right? The goal of the game is to let the items pile up, so you need to add a wall on each side of the screen.

To do this, simply create two new GB2Node objects. They will be out of the screen to the left and right.

Since GB2Nodes add themselves to the current physics simulation, you don’t need to add them manually. They are not represented graphically, so creating them will suffice.

Add these lines to the init in GameLayer.mm, right after the floor layer:

GB2Node *leftWall = [[GB2Node alloc] initWithStaticBody:nil node:nil];
[leftWall addEdgeFrom:b2Vec2FromCC(0, 0) to:b2Vec2FromCC(0, 10000)];
 
GB2Node *rightWall = [[GB2Node alloc] initWithStaticBody:nil node:nil];
[rightWall addEdgeFrom:b2Vec2FromCC(480, 0) to:b2Vec2FromCC(480, 10000)];

Build and run. See how the objects are now kept inside the screen by your walls?

This looks nice, but there is still something missing. I think the objects should make some noise when colliding with each other. Don’t you agree?

I don’t want the objects to make sounds all the time, just when they hit each other at a decent speed. So you’ll check the object’s velocity, and play a sound only when it collides at a fast enough speed.

Add this code to Object.mm:

-(void) beginContactWithObject:(GB2Contact*)contact
{
    b2Vec2 velocity = [self linearVelocity];
 
    // play the sound only when the impact is high
    if(velocity.LengthSquared() > 3.0)
    {
        // play the item hit sound
        // pan it depending on the position of the collision
        // add some randomness to the pitch
        [[SimpleAudioEngine sharedEngine] playEffect:[NSString stringWithFormat:@"%@.caf", objName]
                    pitch:gFloatRand(0.8,1.2)
                    pan:(self.ccNode.position.x-240.0f) / 240.0f
                    gain:1.0 ];    
 
    }
}

The above method must be named beginContactWithObject so that it will be automatically called by GBox2D each time two objects collide.

The linearVelocity method gives you the velocity of the object. Calling Length or LengthSquared on the object delivers the velocity’s value. I prefer using LengthSquared when comparing with a constant value, since it doesn’t require calculating the square root of the value.

You’ll play the sound with a call to SimpleAudioEngine‘s playEffect method. The first parameter is the name of the audio file.

Remember that to make your life easier I gave the sound effects the same name as the objects and sprites. So you can use the objName you stored earlier to get the right sound file. Use NSString to append .caf to the name.

Add some variation to the pitch by using gFloatRand with 0.8 and 1.2. It would be boring if every object made the same sound all the time.

The last trick to apply is to pan the sound’s source to the position of the object. Pan allows values between -1.0 and 1.0. The object’s x position (in points) will be somewhere between 0 and 480, so subtracting 240 and dividing by 240 will deliver that range.

If you want objects to make a sound when they hit the floor without rewriting a lot of the code, add the following method which forwards the object-floor collision to the object-object collision to Object.mm:

-(void) beginContactWithFloor:(GB2Contact*)contact
{
    [self beginContactWithObject:contact];
}

Compile, run, and see how the objects drop and make a sound upon collision.

Ah, but there is one more thing I don’t like about your game right now. The first item drops and then pauses in mid-air while the sound engine is initialized.

This won’t be a problem once you add the theme music, since the music will initialize the sound engine right away. But if you want to fix this now, first add an import statement to the top of GameLayer.mm:

#import "SimpleAudioEngine.h"

Then, add a call to SimpleAudioEngine’s shared object inside GameLayer‘s init selector:

[SimpleAudioEngine sharedEngine];

The above implementation plays the same basic sound (with pitch variations) for all falling object collisions. If you’re ambitious, you could play different sounds depending upon the types of objects colliding. That is, play one sound when a canteen hits a canteen, and another when a banana hits a canteen…

Another way to improve this code would be to vary the gain of the effect with the speed of the collision.

Where to Go From Here?

If you don’t have it already, here is all of the source code for this tutorial series.

You’ve now reached the end of Part One of the MonkeyJump tutorial! The project in its current form is available in the source code zip in the folder called 2-DroppingObjects.

Stay tuned for part two, where you’ll add your hero (the monkey), make him move and interact with objects – even make him teleport! Plus injury and death, life-giving bananas, a drop warning light… there’s lots of exciting stuff ahead.

Before you get there, let me know if you have any questions or comments about what we’ve done so far. I’ll be following the discussions in the forum below.

Owner of CodeAndWeb Developer of TexturePacker and PhysicsEditor. Co-author of "Learn cocos2d game development with iOS 5"

User Comments

11 Comments

  • I think there is issue with file extensions. I originally though I made a typo when I got error "File must contain -586h@x2" but now I think it is because

    tutorial first says: "Next, set the Data file to background-568h@2x.plist"

    and then bit later:

    "Set the Main extension to -586h@2x.. "

    With "568" in the former vs "586" in the latter. Which one should it be? Or am I wrong entirely?
    hardcoder
  • Got all geared up to do this but the "tutorial source code" is missing .!!
    Can you please put it back up.?
    Cheers!
    Mozzy
  • Thank you for updating this great tutorial Andreas.

    For anyone having trouble downloading the source files, copy and paste this into your browser:

    http://cdn2.raywenderlich.com/downloads/MonkeyJump2.zip

    The link in the articles uses all lower case for the file name, but the actual name is mixed case. The 404 error logs probably look pretty funny from all of the things I typed in earlier today to try to find the file.

    Now, off to kill the monkey!
    DurdenSC
  • Thanks for the very informative tutorial. I am using Physics Editor for the animation in my game. I know you said that:


    The first idea that comes to mind is to add a collision polygon for each animation frame to make a perfect match for each phase of the animation. But this would be a bad idea.

    First of all, the frequent exchange of fixtures would consume a good deal of CPU power. But the bigger problem is that with each new shape, the mass of the monkey would change!

    This is because Box2d calculates mass based on a polygons area and density. If you change the polygon, the monkey will gain or lose weight! And this will lead to the physics simulation behaving erratically.



    but my sprites have very interesting movements and using the tracer method or several shapes is not working well for me, especially for the collision detection. I was thinking of changing the Box2d body fixture, not every frame, but for key frame changes. Is there a way I can solve the mass issue and other related issues you stated above?
    mikeme
  • Sorry about the capitalization issue with the download - fixed!
    rwenderlich
  • hi, your tutorials are great, is it possible for you to translate them (at least this one) to Cocos2d-x for Android (I'm currently developing for Blackberry Playbook, but they work well
    nosmirck
  • This is great!
    Haven't read this article yet, but got the source code working.
    Thank for the effort.
    ffxiangyu
  • Hi there,

    I actually followed up till the end of the Part I of this tutorial of creating the MonkeyJump. It worked fine, except that I ended up with a decreased frame rates when the falling objects started to pile up, while there are not that many objects, say like 30?

    Still figuring the reason ...

    Also, when is Part II going to be made available?

    Thanks,
    chaobin
  • In GB2Engine.mm I see this code in update method:

    Code: Select all
        [self iterateObjectsWithBlock:^(GB2Node *o) {
            // update position, rotation
            [o updateCCFromPhysics];
           
            if(o.deleteLater)
            {
                // destroys the body and removes the object from the scene
                [o deleteNow];
            }
        }];

    - (void) iterateObjectsWithBlock:(GB2NodeCallBack)callback1
    {
       for (b2Body* b = world->GetBodyList(); b; b = b->GetNext())
        {       
            // get the object
            callback1((GB2Node*)(b->GetUserData()));
        }   
    }


    Is this code the same or not?:

    Code: Select all
        for (b2Body* b = world->GetBodyList(); b; b = b->GetNext())
        {
             GB2Node *o = ((GB2Node*) (b->GetUserData()));
             // update position, rotation
            [o updateCCFromPhysics];
           
            if(o.deleteLater)
            {
                // destroys the body and removes the object from the scene
                [o deleteNow];
            }
        }


    Maybe there are some nuances?
    Irix
  • any guess why this would crash for me? I am anxious to get started.
    error :Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Format not supported'
    dmagnin
  • Thinks for your tutorials.I want to learn GBox2D serious.Can you game me some more documentation? Think you very much! Do you have other examples with using GBox2D? I intend to develop a game,and your answer helpful to me.
    Pupils

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!

Hang Out With Us!

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


Coming up in May: Procedural Level Generation in Games with Kim Pedersen.

Sign Up - May

Coming up in June: WWDC Keynote - Podcasters React! with the podcasting team.

Sign Up - June

Vote For Our Next Book!

Help us choose the topic for our next book we write! (Choose up to three topics.)

    Loading ... Loading ...

Our Books

Our Team

Tutorial Team

  • Jean-Pierre Distler
  • Felipe Laso Marsetti

... 55 total!

Editorial Team

... 21 total!

Code Team

  • Orta Therox

... 1 total!

Translation Team

  • Sonic Zhao
  • Team Tyran

... 38 total!

Subject Matter Experts

  • Richard Casey

... 4 total!