Here's a Cocos2d-x Box2D tutorial on how to make a physics-based game we've dubbed Arctic Fling. Throw fish at the opposing penguin to knock them over. You can see the completed project on GitHub

2013-08-23 15.06.23

Here are the steps we'll take to create this game.

  • Create a Cocos2D-x project with Box2D
  • Create the artwork and Box2D Shapes
  • Change touches to throw fish
  • Place bases using friction joints
  • Use a Contact Listener to determine collision behavior
  • End the game
  • Delete unneeded fish

Create a Cocos2d-x Project With Box2D

I used the Cocos2d-x Git repo using the install_xcode_templates.sh script, then in XCode created a Cocos2d-x with Box2D project.

Create the Artwork and Box2D Shapes

The fastest way I've found to set up Box2D shapes is to use Physics Editor application. There are a few things to make these work well in our project:

  • Change the Exporter to Box2D generic (PLIST)
  • Change the PTM-Ratio to match your Project (it's probably 32.0)
  • Set your anchor points to Relative 0.5/0.5
  • Density, Restitution and Friction can be different based on your preferences. I usually kept the base's density twice what the fish's was, and made the fish's restitution was generally 0.2. More restitution means more bouncy.

I've provided the graphics, shapes and plist I used as an example. The art was lovingly created by Diana Pham (GitHub)(LinkedIn) . Feel free to try out different options to see what changes. Get them here.

We made a great team.

Change Touches to Throw Fish

The first thing we’re going to do is change the addNewSpriteAtPosition method to include a name and velocity, as well as change the method to use our Physics Editor created shapes. In HelloWorldScene.h, change the addNewSpriteAtPosition to this:
[cpp]b2Body * addNewSpriteAtPosition(string name, cocos2d::CCPoint p, cocos2d::CCPoint velocity);
[/cpp]

Add these two helper files to your Classes directory, and add them to your XCode project
<a href=”https://raw.github.com/AndreasLoew/PhysicsEditor-Cocos2d-x-Box2d/master/Demo/generic-box2d-plist/GB2ShapeCache-x.cpp”>GB2ShapeCache-x.cpp
<a href=”https://raw.github.com/AndreasLoew/PhysicsEditor-Cocos2d-x-Box2d/master/Demo/generic-box2d-plist/GB2ShapeCache-x.h”>GB2ShapeCache-x.h

Also in HelloWorldScene.cpp, add it to your includes
[cpp]#include "GB2ShapeCache-x.h"[/cpp]

And load the shape cache. At the top of HelloWorld::HelloWorld(), add this
[cpp]
GB2ShapeCache *sc = GB2ShapeCache::sharedGB2ShapeCache();
sc->addShapesWithFile("shapes.plist");
[/cpp]

[cpp]
b2Body* HelloWorld::addNewSpriteAtPosition(string name, CCPoint p, CCPoint velocity)
{
CCLOG("Add sprite %0.2f x %02.f",p.x,p.y);

// Create a sprite
CCSprite *sprite = CCSprite::create((name+".png").c_str());

sprite->setPosition(p);

addChild(sprite);

// Create the Box2D body
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.angle = ccpToAngle(velocity);
bodyDef.position.Set(p.x/PTM_RATIO, p.y/PTM_RATIO);
bodyDef.userData = sprite;
b2Body *body = world->CreateBody(&bodyDef);

// add the fixture definitions to the body

GB2ShapeCache *sc = GB2ShapeCache::sharedGB2ShapeCache();
sc->addFixturesToBody(body, name.c_str());
sprite->setAnchorPoint(sc->anchorPointForShape(name.c_str()));

// Set them in motion
b2Vec2 f = 6.0 * b2Vec2(velocity.x, velocity.y); // 6.0 is just to make it more powerful
body->ApplyForceToCenter(f);

return body;
}

[/cpp]

In HelloWorldScene.h, add the following public method:

[cpp]virtual void ccTouchesEnded(cocos2d::CCSet* touches, cocos2d::CCEvent* event);[/cpp]
This means we're going to be defining this method in our class.

In HelloWorldScene.cpp, add the following:
[cpp]
string names[] = {
"fishPink",
"fishPurple",
"fishBlue",
"fishYellow",
"fishOrange",
"fishRed",
"fishGreen"
};

void HelloWorld::ccTouchesEnded(CCSet* touches, CCEvent* event)
{
if(!gameInProgress) return; // Ignore input when game is ended

//Add a new body/atlas sprite at the touched location
CCSetIterator it;
CCTouch* touch;

for( it = touches->begin(); it != touches->end(); it++)
{
touch = (CCTouch*)(*it);

if(!touch)
break;

CCPoint location = touch->getLocationInView();
CCPoint startLocation = touch->getStartLocationInView();

CCPoint velocity = ccpSub(touch->getLocationInView(), touch->getStartLocationInView());
velocity = CCPointMake(velocity.x, -1.0* velocity.y);
startLocation = CCDirector::sharedDirector()->convertToGL(startLocation);
location = CCDirector::sharedDirector()->convertToGL(location);

CCSize s = CCDirector::sharedDirector()->getWinSize();

string spriteName = names[rand()%7];

b2Body * blast = addNewSpriteAtPosition( spriteName, location, velocity );
blast->SetAngularVelocity( (rand() % 6) - 3);
CCSprite * blastSprite = (CCSprite *)blast->GetUserData();
// Left side 3, right side 4
blastSprite->setTag(( startLocation.x < s.width/2 ) ? 3:4);
}
}
[/cpp]

Right now, we have the sprite being created at the location the touch ended, and in the direction the touch moved. This is like a flick action. You could change these around however you like, such as make them go the opposite direction, like a slingshot. You could also start the sprite at the beginning touch spot. Try things out!

You can also adjust the gravity to your liking in the initPhysics() function. I've set mine to
[cpp]gravity.Set(0.0f, -4.0f);[/cpp]

(iOS Only) Allow Multitouch

In the AppController.mm, right after *__glView is defined (about line 39) add the following:
[cpp]__glView.multipleTouchEnabled = true;[/cpp]

If you forget this, there might be some strange behavior when you have two people flinging fish at the same time.

Place Bases Using Friction Joints

Normally, physics object have no rotational friction, which means when they start spinning they will keep going until something stops them. We want to add a little challenge to our game, so we want the bases to stop spinning after each hit. We’re going to define a max torque variable here. At the top of HelloWorldScene.cpp, near your other define statement, add this:
[cpp]
#define BASE_MAX_TORQUE 30
[/cpp]
In HelloWorld::HelloWorld(), remove code specific to the demo. After this->initPhysics(); add this
[cpp]
// Base 1
base1 = addNewSpriteAtPosition("fullModel", ccp(s.width/4.0, s.height/2), CCPointZero);
CCSprite * base1Sprite = (CCSprite*)base1->GetUserData();
base1Sprite->setTag(1);

// -Friction
b2FrictionJointDef base1Friction;
base1Friction.Initialize(base1, groundBody, base1->GetWorldCenter());
base1Friction.maxTorque = BASE_MAX_TORQUE;
base1Friction.collideConnected = true;
world->CreateJoint(&base1Friction);

// -Pivot
b2RevoluteJointDef base1JointDef;
base1JointDef.Initialize(base1, groundBody, base1->GetWorldCenter());
world->CreateJoint(&base1JointDef);

// Base 2
base2 = addNewSpriteAtPosition("fullModel-right", ccp(3.0*s.width/4.0, s.height/2), CCPointZero);
CCSprite * base2Sprite = (CCSprite*)base2->GetUserData();
base2Sprite->setTag(2);

// -Friction
b2FrictionJointDef base2Friction;
base2Friction.Initialize(base2, groundBody, base2->GetWorldCenter());
base2Friction.maxTorque = BASE_MAX_TORQUE;
base2Friction.collideConnected = true;
world->CreateJoint(&base2Friction);

// -Pivot
b2RevoluteJointDef base2JointDef;
base2JointDef.Initialize(base2, groundBody, base2->GetWorldCenter());
world->CreateJoint(&base2JointDef);
[/cpp]

There, now our bases are fixed to a specific point on the screen, and will only spin a little when hit by fish.

Determine a Winner

Define the angle you want for the winner to be set
[cpp]
#define MAX_ANGLE (M_PI / 3.0)
[/cpp]
This will make it Pi/3, or roughly 60 degrees.

Also create a variable to know when the game is over, and a label to display who has won
In HelloWorldScene.h, add a private variable:
[cpp]
bool gameInProgress;
CCLabelTTF *label;

[/cpp]
In HelloWorldScene.cpp, in the HelloWorld() function, add
[cpp]
gameInProgress = true;
label = CCLabelTTF::create("Arctic Fling", "Marker Felt", 32);
addChild(label, 0);
label->setColor(ccc3(0,0,255));
label->setPosition(ccp( s.width/2, s.height-50));

[/cpp]

We can ignore touches once the game has ended. At the top of ccTouchesEnded, add
[cpp]
if(!gameInProgress) return; // Ignore input when game is ended
[/cpp]

Then in the update() function, add this to the bottom
[cpp]
if(gameInProgress){
// Check for game winning conditions
if( base1->GetAngle() > MAX_ANGLE || base1->GetAngle() < -1.0 * MAX_ANGLE ){
// Base 2 wins
label->setString("Player 2 Wins");
// Unhook penguin 1 from the base

// Show new game button
gameInProgress = false;

};

if(base2->GetAngle() > MAX_ANGLE || base2->GetAngle() < -1.0 * MAX_ANGLE){
// Base 2 wins
label->setString("Player 1 Wins");
// Unhook penguin 2 from the base

// Show new game button
gameInProgress = false;
};

}

[/cpp]
This will check the angle of the base each time and end the game when the winner is determined.

Delete Unneeded Fish

You'll notice that the fish end up piling up at the bottom of the screen. We should get rid of fish that fall to the bottom, or fish that are created on top of our own base.
This will be added to the tutorial soon. This involves creating a Contact Listener class, tagging sprites and deleting the right sprites.