Intro to Box2D with Cocos2D 2.X Tutorial: Bouncing Balls

Brian Broom Brian Broom
Bouncing Ball Example

Bouncing Ball Example

Update 1/9/2013: Fully updated for Cocos2D 2.1-beta4 (original post by Ray Wenderlich, update by Brian Broom).

This tutorial helps get you started with Box2D with Cocos2D by showing you how to create a simple app that shows a ball that you can bounce around the screen by rotating your iPhone with the accelerometer.

This tutorial is based on an excellent example by Kyle from iPhoneDev.net, but updated to the latest version of Cocos2D and with some more detailed explanations of how things work. It also has some elements of the sample project that is included in the cocos2d Box2d Application template, but explains how things work step-by-step.

This tutorial assumes that you’ve already gone through the tutorial on how to create a simple game with Cocos2D, or have equivalent knowledge.

So anyway, let’s get started learning Box2D with Cocos2D and start bouncing away!

Creating An Empty Project

Begin by creating a new project in XCode by selecting the cocos2d v2.x cocos2d iOS with Box2d Application template, and naming your project Box2D. If you compile and run this template, you’ll see a pretty cool example demonstrating many aspects of Box2D. However, for the purposes of this tutorial, we’re going to create everything from scratch so we can gain a good understanding of how things work.

So let’s clear out the template so we have a known-good starting point. Replace HelloWorldLayer.h with the following:

#import "cocos2d.h"
 
#define PTM_RATIO 32.0
 
@interface HelloWorldLayer : CCLayer {   
}
 
+ (id) scene;
 
@end

And replace HelloWorldLayer.mm with the following:

#import "HelloWorldLayer.h"
 
@implementation HelloWorldLayer
 
+ (id)scene {
 
    CCScene *scene = [CCScene node];
    HelloWorldLayer *layer = [HelloWorldLayer node];
    [scene addChild:layer];
    return scene;
 
}
 
- (id)init {
 
    if ((self=[super init])) {
    }
    return self;
}
 
@end

If you compile and run that, you should see a blank screen. Ok great – now let’s start creating our Box2D scene.

The Box2D World In Theory

Before we go any further, let’s talk a bit about how things work in Box2D.

The first thing you need to do when using Cocos2D is to create a world object for Box2D. The world object is the main object in Cocos2D that manages all of the objects and the physics simulation.

Once we’ve created the world object, we need to add some bodies to the world. Bodies can be objects that move around in your game like ninja stars or monsters, but they can also be static bodies that don’t move such as platforms or walls.

There are a bunch of things you need to do to create a body – create a body definition, a body object, a shape, a fixture definition, and a fixture object. Here’s what all of these crazy things mean!

  • You first create a body definition to specify initial properties of the body such as position or velocity.
  • Once you set that up, you can use the world object to create a body object by specifying the body definition.
  • You then create a shape representing the geometry you wish to simulate.
  • You then create a fixture definition – you set the shape of the fixture definition to be the shape you created, and set other properties such as density or friction.
  • Finally you can use the body object to create a fixture object by specifying the fixture definition.
  • Note that you can add as many fixture objects to a single body object. This can come in handy when creating complex objects.

Once you’ve added all of the bodies you like to your world, Box2D can take over and do the simulation – as long as you call its “Step” function periodically so it has processing time.

But note that Box2D only updates its internal model of where objects are – if you’d like the Cocos2D sprites to update their position to be in the same location as the physics simulation, you’ll need to periodically update the position of the sprites as well.

Ok so now that we have a basic understanding of how things should work, let’s see it in code!

The Box2D World In Practice

Ok, before you begin download a picture of a ball I made, along with the retina version, that we’re going to add to the scene. Once you have it downloaded, drag it to the Resources folder in your project and make sure that “Copy items into destination group’s folder (if needed)” is checked.

Next, take a look at this line we added earlier in HelloWorldLayer.h:

#define PTM_RATIO 32.0

This is defining a ratio of pixels to “meters”. When you specify where bodies are in Cocos2D, you give it a set of units. Although you may consider using pixels, that would be a mistake. According to the Box2D manual, Box2D has been optimized to deal with units as small as 0.1 and as big as 10. So as far as length goes people generally tend to treat it as “meters” so 0.1 would be about teacup size and 10 would be about box size.

So we don’t want to pass pixels in, because even small objects would be 60×60 pixels, way bigger than the values Box2D has been optimized for. So we need to have a way to convert pixels to “meters”, hence we can just define a ratio like the above. So if we had a 64 pixel object, we could divide it by PTM_RATIO to get 2 “meters” that Box2D can deal with for physics simulation purposes.

Ok, now for the fun stuff. Add the following to the top of HelloWorldLayer.h:

#import "Box2D.h"

Also add member variables to your HelloWorldLayer class:

b2World *_world;
b2Body *_body;
CCSprite *_ball;

Then add this to your init method in HelloWorldLayer.mm:

CGSize winSize = [CCDirector sharedDirector].winSize;
 
// Create sprite and add it to the layer
_ball = [CCSprite spriteWithFile:@"ball.png" rect:CGRectMake(0, 0, 52, 52)];
_ball.position = ccp(100, 300);
[self addChild:_ball];
 
// Create a world
b2Vec2 gravity = b2Vec2(0.0f, -8.0f);
_world = new b2World(gravity);
 
// Create ball body and shape
b2BodyDef ballBodyDef;
ballBodyDef.type = b2_dynamicBody;
ballBodyDef.position.Set(100/PTM_RATIO, 300/PTM_RATIO);
ballBodyDef.userData = _ball;
_body = _world->CreateBody(&ballBodyDef);
 
b2CircleShape circle;
circle.m_radius = 26.0/PTM_RATIO;
 
b2FixtureDef ballShapeDef;
ballShapeDef.shape = &circle;
ballShapeDef.density = 1.0f;
ballShapeDef.friction = 0.2f;
ballShapeDef.restitution = 0.8f;
_body->CreateFixture(&ballShapeDef);
 
[self schedule:@selector(tick:)];

A few lines should be familiar from the Cocos2d tutorial, but much of it is new. Let’s explain it bit by bit. I’ll repeat the code here section by section to make it easier to explain.

CGSize winSize = [CCDirector sharedDirector].winSize;
 
// Create sprite and add it to the layer
_ball = [CCSprite spriteWithFile:@"ball.png" rect:CGRectMake(0, 0, 52, 52)];
_ball.position = ccp(100, 300);
[self addChild:_ball];

First, we add the sprite to the scene just like we normally would using Cocos2D. If you’ve followed the previous Cocos2D tutorials there should be no surprises here.

// Create a world
b2Vec2 gravity = b2Vec2(0.0f, -8.0f);
_world = new b2World(gravity);

Next, we create the world object. When we create this object, we need to specify an initial gravity vector. We set it here to -8 along the y axis, so bodies will appear to drop to the bottom of the screen.

// Create ball body and shape
b2BodyDef ballBodyDef;
ballBodyDef.type = b2_dynamicBody;
ballBodyDef.position.Set(100/PTM_RATIO, 300/PTM_RATIO);
ballBodyDef.userData = _ball;
_body = _world->CreateBody(&ballBodyDef);
 
b2CircleShape circle;
circle.m_radius = 26.0/PTM_RATIO;
 
b2FixtureDef ballShapeDef;
ballShapeDef.shape = &circle;
ballShapeDef.density = 1.0f;
ballShapeDef.friction = 0.2f;
ballShapeDef.restitution = 0.8f;
_body->CreateFixture(&ballShapeDef);

Next, we create the ball body.

  • We specify the type as a dynamic body. The default value for bodies is to be a static body, which means it does not move and will not be simulated. Obviously we want our ball to be simulated!
  • We set the user data parameter to be our ball CCSprite. You can set the user data parameter on a body to be anything you like, but usually it is quite convenient to set it to the sprite so you can access it in other places (such as when two bodies collide).
  • We have to define a shape – a circle shape. Remember that Box2d doesn’t look at the sprite image, we have to tell it what the shapes are so it can simulate them correctly.
  • Finally we set some parameters on the fixture. We’ll cover what the parameters mean later on.
[self schedule:@selector(tick:)];

The last thing in the method is to schedule a method named tick to be called as often as possible. Note that this isn’t the ideal way to do things – it’s better if the tick method is called at a set frequency (such as 60 times a second). However, for this tutorial we’re going to stay as-is.

So let’s write the tick method! Add this after your init method:

- (void)tick:(ccTime) dt {
 
    _world->Step(dt, 10, 10);
    for(b2Body *b = _world->GetBodyList(); b; b=b->GetNext()) {    
        if (b->GetUserData() != NULL) {
            CCSprite *ballData = (CCSprite *)b->GetUserData();
            ballData.position = ccp(b->GetPosition().x * PTM_RATIO,
                                    b->GetPosition().y * PTM_RATIO);
            ballData.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle());
        }        
    }
 
}

The first thing we do here is to call the “Step” function on the world so it can perform the physics simulation. The two parameters here are velocity iterations and position iterations – you should usually set these somewhere in the range of 8-10.

The next thing we do is make our sprites match the simulation. So we iterate through all of the bodies in the world looking for those with user data set. Once we find them, we know that the user data is a sprite (because we made it that way!), so we update the position and angle of the sprite to match the physics simulation.

One last thing to add – cleanup! Add this to the end of the file:

- (void)dealloc {    
    delete _world;
    _body = NULL;
    _world = NULL;
    [super dealloc];
}

Give it a compile and run, and you should see a ball drop down and off the screen. Whoops, we didn’t define a floor for the ball to bounce on.

Falling on the Floor

To represent the floor, we create an invisible edge at the bottom of the screen of our iPhone. We do this by completing the following steps.

  • We first create a body definition and specify that the body should be positioned in the lower left corner. Since we don’t change the body type, this will default to a static object, which is what we want.
  • We then use the world object to create the body object.
  • We then create an edge shape for the bottom edge of the screen. This “shape” is actually just a line. Note that we have to convert the pixels into “meters” by using our conversion ratio as we discussed above.
  • We create a fixture definition, specifying the edge shape.
  • We then use the body object to create a fixture object for the shape.
  • Also note that one body object can contain multiple fixture objects!

Add the following code to the init method, after creating the world and before the ball definition.

	// Create edges around the entire screen
	b2BodyDef groundBodyDef;
	groundBodyDef.position.Set(0,0);
 
	b2Body *groundBody = _world->CreateBody(&groundBodyDef);
	b2EdgeShape groundEdge;
	b2FixtureDef boxShapeDef;
	boxShapeDef.shape = &groundEdge;
 
	//wall definitions
	groundEdge.Set(b2Vec2(0,0), b2Vec2(winSize.width/PTM_RATIO, 0));
	groundBody->CreateFixture(&boxShapeDef);

Now when we compile and run, the ball will bounce up in the air several times before coming to rest.

Bouncing Ball Screenshot

What About Horizontal Motion?

Now that we have the basics down, lets make something more interesting – an invisible foot that kicks the ball every few seconds. Define a new method in HelloWorldLayer.h:

- (void)kick;

and add the method in HelloWorldLayer.mm:

- (void)kick {
    b2Vec2 force = b2Vec2(30, 30);
    _body->ApplyLinearImpulse(force,_body->GetPosition());
}

ApplyLinearImpulse puts a force on the ball, making it move. How fast the ball moves is based on its mass (from the density property we set earlier). Try different values for density, and for the force to find values you like. The coordinate system is the same as Cocos2d, with positive x and y being up and to the right.

Add this line to the init method to run the kick method every 5 seconds.

    [self schedule:@selector(kick) interval:5.0];

If you build and run the project now, the ball will fly offscreen after it is kicked. Let’s go ahead and define the other walls. Find the wall definitions section of the init method, and add the following lines. Note: Each wall needs two lines of code, one to Set (define) the coordinates, and one to add the edge as a fixture of the ground object.

    groundEdge.Set(b2Vec2(0,0), b2Vec2(0,winSize.height/PTM_RATIO));
    groundBody->CreateFixture(&boxShapeDef);
 
    groundEdge.Set(b2Vec2(0, winSize.height/PTM_RATIO),
                   b2Vec2(winSize.width/PTM_RATIO, winSize.height/PTM_RATIO));
    groundBody->CreateFixture(&boxShapeDef);
 
    groundEdge.Set(b2Vec2(winSize.width/PTM_RATIO, winSize.height/PTM_RATIO),
                   b2Vec2(winSize.width/PTM_RATIO, 0));
    groundBody->CreateFixture(&boxShapeDef);

Now you can build and run the project and watch the ball bounce around your screen.

Integrating Touch

Since our HelloWorldLayer is still a cocos2d layer, we can use all the tools we have learned for touch events. To show how they interact with Box2d, lets change our app so that when we touch the screen, it kicks the ball to the left.

To enable touch events, add this line to the init method in HelloWorldLayer.mm:

    [self setTouchEnabled:YES];

and add this method to handle the events

- (void)ccTouchesBegan:(UITouch *)touch withEvent:(UIEvent *)event {
    b2Vec2 force = b2Vec2(-30, 30);
    _body->ApplyLinearImpulse(force, _body->GetPosition());
}

Just like before, we are using the ApplyLinearImpulse method to add a force to the ball. Using a negative x value for the force will push it to the left.

A Note On The Simulation

As promised let’s talk about those density, friction, and restitution variables that we set on the ball.

  • Density is mass per unit volume. So the more dense an object is, the more mass it has, and the harder it is to move.
  • Friction is a measure of how hard it is for objects to slide against each other. This should be in the range of 0 to 1. 0 means there is no friction, and 1 means there is a lot of friction.
  • Restitution is a measure of how “bouncy” an object is. This should usually be in the range of 0 to 1. 0 means the object will not bounce, and 1 means the bounce is perfectly elastic, meaning it will bounce away with the same velocity that it impacted an object.

Feel free to play around with these values and see what difference it makes. Try to see if you can make your ball more bouncy!

Finishing Touches

It would be cool if we could get the ball to bounce around the screen as we tilt our device around. For one thing that will help us test that we’ve got all the boundaries working! This is quite easy to do. Add the following to your init method:

[self setAccelerometerEnabled:YES];

Then add the following method somewhere in the file:

- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
 
    // Landscape left values
    b2Vec2 gravity(acceleration.y * 30, -acceleration.x * 30);
    _world->SetGravity(gravity);    
}

Finally, click on the project target at the top of the Project Navigator sidebar. Make sure your target (Box2D) is selected, and on the Summary tab. In the Supported Interface Orientations section, click on the ‘Landscape Right’ button to deselect it. The Landscape Left button should be the only one selected (dark grey). We don’t want iOS to change the orientation of the app when the phone is rotated.

Landscape Left only

Supported Interface Orientations setting for the accelerometer section.

All we’re doing here is setting the gravity vector in the simulation to be a multiple of the acceleration vector. Give it a compile and run (on your device), and you should be able to bounce your ball all around the screen!

Note: Accelerometer data is only available running on a physical device, which requires a paid Apple developer account and a developer certificate installed. See the iOS Provisioning Portal at developer.apple.com.

Where To Go From Here?

Here’s a project with the all of the above code.

If you want to learn more about Box2D, check out the tutorial series on How To Create A Breakout Game with Box2D and Cocos2D!

Brian Broom
Brian Broom

Brian has been tinkering with computers since writing basic programs on IBM PC (with Two! floppy drives). He has done web, database, C++, ruby, and now iOS development. Brian has spent the last few years as a computer science teacher and trainer.

You can reach him by email or on Twitter.

User Comments

8 Comments

  • Hi, i'm new to Box2D. This is a great tutorial!!!
    But I found this problem:
    the article uses the lines
    [self setTouchEnabled:YES]; and [self setAccelerometerEnabled:YES]
    and XCode tells me tha these methods are "not found".

    At the begininng I didn't understand the warnings but then i found that the correct lines for me are:
    [self setIsTouchEnabled:YES]; and [self setIsAccelerometerEnabled:YES];
    Now the code work properly!

    I just want to share this problem with all of you!
    Bye
    Nic :D
    Niccolò Passolunghinicopasso
  • Hi, I'm new to Objective-C, Xcode, Cocos2D and Box2D. My question is do we need to learn C++ in order to use Box2D. The tutorial doesn't make any mention of C++ but the Box2D part is written in C++, right? The files are .mm extension but the code just seems to be mixed into the usual Objective-C functions. So, if you want to use Box2D with an obj-c app do you have to learn C++ first?
    ptc79
  • Hello,
    I'm trying to add more balls with a button click.

    I wrote this:
    Code: Select all

    [...]
    CCMenuItem *addMenuItem = [CCMenuItemImage itemWithNormalImage:@"ButtonPlus.png" selectedImage:@"ButtonPlusSel.png" target:self selector:@selector(addNewBall)];
            addMenuItem.position = ccp(winSize.width - (addMenuItem.contentSize.width/2), winSize.height - (addMenuItem.contentSize.height/2));
            CCMenu *addMenu = [CCMenu menuWithItems:addMenuItem, nil];
            addMenu.position = CGPointZero;
            [self addChild:addMenu];
    [...]

    - (void)addNewBall {   
        CCSprite* newBall = [CCSprite spriteWithFile:@"ball.png" rect:CGRectMake(0, 0, 52, 52)];
        newBall.position = ccp(0, 300);
        [self addChild:newBall];
       
        b2Body* newBallBody;
       
        b2BodyDef ballBodyDef;
        ballBodyDef.type = b2_dynamicBody;
        ballBodyDef.position.Set(0/PTM_RATIO, 300/PTM_RATIO);
        ballBodyDef.userData = newBall;
        newBallBody = _world->CreateBody(&ballBodyDef);
       
        b2CircleShape circle;
        circle.m_radius = 26.0/PTM_RATIO;
       
        b2FixtureDef ballShapeDef;
        ballShapeDef.shape = &circle;
        ballShapeDef.density = 1.0f;
        ballShapeDef.friction = 0.2f;
        ballShapeDef.restitution = 0.8f;
        newBallBody->CreateFixture(&ballShapeDef);
    }


    But it won't add any new balls if I press on the button.
    Do I have to tell the program that there's a new body it has to deal with or anything if it's not created in the init method?

    ~VitalRemains
    VitalRemains
  • I'm doing the game which you tile the phone to control the ball roll on to the table. I tried to simulate the ball look like the steel ball in the real life but it wasn't successful as i expected. In the test, i changed 3 things.
    1. GRAVITY - use accelerator by gravity ratio
    2. LINEAR DAMPING of the ball
    3. SENSITIVE for accelerator - i don't need tile 100% the phone to archive fully gravity. Instead of, just tile phone 40% (maybe not important)

    Finally, i found some number to make it look like real (about 70% likely, i hope so ^^). But the problem appeared. I choose the GRAVITY about 50, Leaner Damping of the ball about 5 so that when the ball hit the wall, it isn't bounce again :(

    Who can help me to fix this problem or change something to look like great ?!? I attached my project to testing in this email if anyone need that
    minhtam
  • Hi
    Thanks for writing such a nice tutorial. It really helped me in learning box2D. Currently I am creating a game in box2d and have come across a strange problem in it. Can you please help me in fixing it. I have posted it here http://stackoverflow.com/questions/1804 ... its-torque
    Thanks and Best Regards
    aqueelmirza
  • hi, i am new to box2d. i created a world with gravity -10. i know all the bodies by default adopt this gravity while creating their body, but i want to set gravity for a particular body to 0. how to set it?
    and i gone through various blogs, found a method "SetGravity" but in my project i didn't found that method. so plz help me how to set that, i'm using cocos2d1.0..
    sagarsandy
  • hi, am new to BOX2D, i ve tried this tuto it looks good but i ve found a little problem
    when i let the ball bounce till it stops at the bottom right corner (that was my test), then i tllt the device the ball stays fixed at this corner

    can u tell me why plzzzz???
    rabio
  • hi,

    i want to create body for my sprite(50*50 size). the body should be like rectangle with top open..
    i have already used "setasbox" method. but as i told it should be like a bucket, which means with top open.
    so i want to create it using setasedge methods, but i'm not getting the exact dimensions for my sprite(50*50 size).
    plzzz suggest me how to create it. it should be like this.. |___|
    sagarsandy

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

  • Sam Davies

... 52 total!

Update Team

  • Ray Fix

... 14 total!

Editorial Team

... 22 total!

Code Team

  • Orta Therox

... 3 total!

Subject Matter Experts

  • Richard Casey

... 4 total!