Cocos2D on the Mac – Part 2 (Physics)

Adding in the Physics

In the last post you learned how to create a Cocos2D game for the Mac. For this post I am going to cover how you can add the Box2D Physics engine to your Cocos2D Mac games. It is really pretty easy, here you will learn how to get Box2D integrated into your project and mess with gravity, next week I will cover more about the Box2D Debug Renderer. I’ve tried to make this post somewhat brief, let me know in the comments what else you would like to see as well as any questions.

Step 1 – Copy the Box2D Folders (excluding the TestBed)
Copy the following Folders into your project.

The TestBed for Box2D includes the Box2D Debug Render Engine for iOS using OpenGL ES. Since we are on the Mac, you want to exclude these for the time being, the next post will show you how to add the older OpenGL Debug Render Engine.

Step 2 – Change the Header Search Path

If you try to compile now you will get a ton of compile errors, over 2000K, since your code does not know where to find the Box2D classes. Fortunately this is easy to fix.

Click on your Project, and then the Info button, and search for “Header Search Path”
Add a header search path to the actual folder location for your Box2D source files (that you copied in Step 1). Be sure to check the recursive checkbox.

Step 3 – Changing to Objective-C++

Since Box2D code is in C++, your Objective-C files have to be Objective-C++, which just means you can mix and match Objective-C and C++ code. Simple rename all of your .m files to .mm
In your games you only need to change the .m files to .mm for any that include Box2D or classes which themselves include Box2D (or any C++).

Rename: Main.m, Cocos2DMacGame2AppDelegate.m, HelloWorldScene.m to .mm versions.

Step 4 – Adding Box2D to your Game

Open the HelloWorldScene.h Header file and add:

#import "Box2D.h"
#define PTM_RATIO 32

In the @interface declaration add:

// HelloWorld Layer
@interface HelloWorld : CCLayer
{
	b2World* world;
}

You should be able to Build your project now without any errors. Test it to make sure everything is good before moving to Step 5.

Step 5 – Creating the Box2D world and ground bodies
Here you are going to add some lines to the init method to setup Box2D, and create ground bodies on the edges of the screen. Without ground bodies your box2d bodies/objects will just fall out of view.
Add this inside the init method:

                // Create the Box2D World
		b2Vec2 gravity;
		gravity.Set(0.0f,-10.0f);
		bool doSleep = true;
		world = new b2World(gravity, doSleep);
		world->SetContinuousPhysics(true);
		
		// Define the ground body.
		b2BodyDef groundBodyDef;
		groundBodyDef.position.Set(0, 0); // bottom-left corner
		
		// Call the body factory which allocates memory for the ground body
		// from a pool and creates the ground box shape (also from a pool).
		// The body is also added to the world.
		b2Body* groundBody = world->CreateBody(&groundBodyDef);
		
		// Define the ground box shape.
		b2PolygonShape groundBox;		
		
		// bottom
		groundBox.SetAsEdge(b2Vec2(0,0), b2Vec2(size.width/PTM_RATIO,0));
		groundBody->CreateFixture(&groundBox,0);
		
		// top
		groundBox.SetAsEdge(b2Vec2(0,size.height/PTM_RATIO), b2Vec2(size.width/PTM_RATIO,size.height/PTM_RATIO));
		groundBody->CreateFixture(&groundBox,0);
		
		// left
		groundBox.SetAsEdge(b2Vec2(0,size.height/PTM_RATIO), b2Vec2(0,0));
		groundBody->CreateFixture(&groundBox,0);
		
		// right
		groundBox.SetAsEdge(b2Vec2(size.width/PTM_RATIO,size.height/PTM_RATIO), b2Vec2(size.width/PTM_RATIO,0));
		groundBody->CreateFixture(&groundBox,0);
		
		// Create a Penguin on the middle of the screen
		[self addNewSpriteWithCoords:ccp(size.width/2,size.height/2)];
		
		// Call the update method 60 times a second
		[self scheduleUpdate];

Note the call to [self scheduleUpdate]. This will call the update method in Step 8 where the physics simulation will occur.

Step 6 – Adding the Box2D shape to the Sprites
Add the addNewSpriteWithCoords method above the init method in HelloWorldScene.mm, it builds on the same method you wrote on the previous post, adding a Box2D body for each Penguin.

-(void) addNewSpriteWithCoords:(CGPoint)p {
	
	
	CCSprite *sprite = [CCSprite spriteWithFile:@"penguino1.png"];
	[self addChild:sprite];
	
	sprite.position = ccp( p.x, p.y);
	
	// Define the dynamic body.
	//Set up a 1m squared box in the physics world
	b2BodyDef bodyDef;
	bodyDef.type = b2_dynamicBody;
	
	bodyDef.position.Set(p.x/PTM_RATIO, p.y/PTM_RATIO);
	bodyDef.userData = sprite;
	bodyDef.allowSleep = false;
	b2Body *body = world->CreateBody(&bodyDef);
	
	// Define another box shape for our dynamic body.
	b2PolygonShape dynamicBox;
	dynamicBox.SetAsBox(0.65f, 0.9f);//These are mid points for our 1m box
	
	// Define the dynamic body fixture.
	b2FixtureDef fixtureDef;
	fixtureDef.shape = &dynamicBox;	
	fixtureDef.density = 3.0f;
	fixtureDef.friction = 0.3f;
	fixtureDef.restitution = 0.3f;
	body->CreateFixture(&fixtureDef);
}

Normally you want your bodies to be able to sleep, in this example we don’t because if you left the penguins at rest, changing the gravity would have no effect of them. In other words, having bodies sleep saves CPU cycles, but if you have a world with few collisions (like this example), then you have to be careful not to get your objects stuck. This being an example, the simplest solution is just to not let physics bodies sleep.

Step 7 – Add the handler for the Mouse and Arrow Keys
In this example the arrow keys change the gravity. If you press up, everything will be pulled towards the sky. Be sure to try out the arrow keys, especially with a few penguins on the screen.

-(BOOL)ccMouseDown:(NSEvent *)event {
	CGPoint location = [event locationInWindow];
	CCLOG(@"Mouse Cick X:%f Y:%f",location.x,location.y);
	
	[self addNewSpriteWithCoords:location];
	return YES;
}
-(BOOL) ccKeyDown:(NSEvent *)event
{
	if ([event modifierFlags] & NSNumericPadKeyMask) { // arrow keys have this mask
        NSString *theArrow = [event charactersIgnoringModifiers];
        unichar keyChar = 0;
        if ( [theArrow length] == 0 )
            return YES;            // reject dead keys
        if ( [theArrow length] == 1 ) {
            keyChar = [theArrow characterAtIndex:0];
            if ( keyChar == NSLeftArrowFunctionKey ) {
                CCLOG(@"Left Arrow pressed");
				CGPoint movePosition = ccp(-1,0);
				
				b2Vec2 gravity;
				gravity.Set(-10.0f,0.0f);
				world->SetGravity(gravity);
				
                return YES;
            }
            if ( keyChar == NSRightArrowFunctionKey ) {
                CCLOG(@"Right Arrow pressed");
				CGPoint movePosition = ccp(1,0);
				
				b2Vec2 gravity;
				gravity.Set(10.0f,0.0f);
				world->SetGravity(gravity);
				
				return YES;
            }
            if ( keyChar == NSUpArrowFunctionKey ) {
				CCLOG(@"Up Arrow Pressed");
				CGPoint movePosition = ccp(0,1);
				
				b2Vec2 gravity;
				gravity.Set(0.0f,10.0f);
				world->SetGravity(gravity);
				
                return YES;
            }
            if ( keyChar == NSDownArrowFunctionKey ) {
				CGPoint movePosition = ccp(0,-1);
				
				b2Vec2 gravity;
				gravity.Set(0.0f,-10.0f);
				world->SetGravity(gravity);
				
				CCLOG(@"Down Arrow Pressed");
                return YES;
            }
        }
    }
    return NO;
}

Step 8 – Update Method to simulate the physics
Add this method above init to update the physics simulation and the positions of sprites associated to physics bodies. This is the same update method found in the Cocos2D iOS Box2D Templates.

-(void)update:(ccTime)dt {
	
	//It is recommended that a fixed time step is used with Box2D for stability
	//of the simulation, however, we are using a variable time step here.
	//You need to make an informed choice, the following URL is useful
	//http://gafferongames.com/game-physics/fix-your-timestep/
	
	int32 velocityIterations = 8;
	int32 positionIterations = 1;
	
	// Instruct the world to perform a single step of simulation. It is
	// generally best to keep the time step and iterations fixed.
	world->Step(dt, velocityIterations, positionIterations);
	
	
	//Iterate over the bodies in the physics world
	for (b2Body* b = world->GetBodyList(); b; b = b->GetNext())
	{
		if (b->GetUserData() != NULL) {
			//Synchronize the AtlasSprites position and rotation with the corresponding body
			CCSprite *myActor = (CCSprite*)b->GetUserData();
			myActor.position = CGPointMake( b->GetPosition().x * PTM_RATIO, b->GetPosition().y * PTM_RATIO);
			myActor.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle());
		}	
	}
	
}

That’s it! Build and Run and click somewhere on the window. You will see a Penguin fall down to the bottom. Try using the arrow keys to have some fun with gravity.

Here is the project source code:
Cocos2DMacGame2 Source

Thanks for reading,
Rod

This post is part of iDevBlogADay, a group of indie iOS development blogs featuring at least two posts per day. You can keep up with your favorite developers through the iDevBlogADay web site, RSS feed, or Twitter.

UA-8095965-3