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

Learn how to make a fun 2D physics game called Monkey Jump in this 3-part tutorial series covering Physics Editor, Texture Packer, and Cocos2D. By .

Leave a rating/review
Save for later
Share
You are currently viewing page 4 of 6 of this article. Click here to view the first page.

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!