I recently gave a presentation at the LiDG on this - the slides can be found here.
The complete set of demo projects for this blog post can be found here (thanks to Terry for pointing out the missing files in the original set of zips).
Box2D is an open source 2D physics engine that has proved popular with iPhone developers.
One thing to bear in mind with Box2D is it is just a physics engine - all the drawing and rendering of the objects is down to you.
The engine is tuned for meters-kilograms-seconds - so you can just feed in objects in pixels units, you should apply some kind of scaling to your objects so that they are in the 0.1 - 10 meter range.
There are several concepts that it’s useful to be familiar with:
- Rigid Body/Body - chunk of matter that is so strong it cannot be compressed
- Shape - 2D pices of collision geometry that is attached to a body
- Friction, Restitution (bouncyness)
- Constraint - Physical connection that removes degrees of freedom from a body
- World - Collection of bodies, shapes, and constraints interacting together
You can download Box2D from sourceforge or Google code. I chose to use a revision from the sourceforge repository that compiles for the iPhone and also matches the online documentation:
svn co https://box2d.svn.sourceforge.net/svnroot/box2d box2d -r 210
Once you’ve dwonloaded the code add the source and include folder to your xCode project as shown in the diagram:
<img style=”display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 115px;” src=”http://2.bp.blogspot.com/_jO69rW-vio4/S2XVi5wjxzI/AAAAAAAAB98/oaxsE4MnLoM/s320/Screen+shot+2010-01-31+at+14.09.13.png” border=”0” alt=”“id=”BLOGGER_PHOTO_ID_5432983321089132338” />
To get Box2D building for the iPhone you need to add to your projects -DTARGET_OS_IPHONE additional CFLAGS
<img style=”display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 154px;” src=”http://4.bp.blogspot.com/_jO69rW-vio4/S2XfpxiBDII/AAAAAAAAB-M/SOvZsL7KqsE/s400/Screen+shot+2010-01-31+at+14.52.48.png” border=”0” alt=”“id=”BLOGGER_PHOTO_ID_5432994434256014466” />
You’ll also need to change your file extension from .m to .mm as Box2D uses C++.
Now we can start to use the Box2D engine to do something useful. For this walkthrough we’re going to create a simple pin-ball game.
<img style=”display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 275px; height: 400px;” src=”http://4.bp.blogspot.com/_jO69rW-vio4/S2XgUxJ92HI/AAAAAAAAB-U/NuAFDy_IrLI/s400/Screen+shot+2010-01-31+at+14.55.35.png” border=”0” alt=”“id=”BLOGGER_PHOTO_ID_5432995172889516146” />
The image above shows our main game view - each of the objects are UIImageViews. We’ll use these to show the locations of the objects in our physics simulation.
The first thing we need to do is create our physics world. This object is responsible or managing the memory for our physics objects and simulating their interactions. Our physics world also has gravity.
.h
// the world
b2World *myWorld;
.m
// create the world
b2AABB worldAABB;
worldAABB.lowerBound.Set(-100.0f, -100.0f);
worldAABB.upperBound.Set(100.0f, 100.0f);
b2Vec2 gravity(0.0f, -2.0f);
myWorld=new World(worldAABB, gravity, true);
When we want to simulate the world we move it forward by a time step. Ideally this time stap should be fast and regular. In the sample code below we’ll be running our world 30 times a second.
-(void) runWorld {
// step the world forward
myWorld->Step(1.0f/30.0f, 10, 10);
}
-(void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
animationTimer=[NSTimer scheduledTimerWithTimeInterval:1.0f/30.0f target:self selector:@selector(runWorld) userInfo:nil repeats:YES];
}
-(void) viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[animationTimer invalidate];
animationTimer=nil;
}
Now we’ve got our world we can add some objects to it. A body has position, rotation and velocity. You can apply forces, torques and impulses to them to.
A body can be static or dynamic. Static bodies do not move and do not interact with other static bodies (we’ll be using static bodies for the planets and edges of the pinball table.
Bodies are the backbone for shapes. Two shapes on the same body never move relative to each other.
A body is built from:
- A body definition
- Position and angle
- Damping (linear and angular)
- One or more shape definitions
- Friction
- Restitution
- Density
The code shown below creates a body for the ball bearing in our game:
b2BodyDef ballBodyDef;
ballBodyDef.position.Set(S2W(self.ballView.center.x),
S2W(self.ballView.center.y));
ballBodyDef.linearDamping=0.5;
ballBodyDef.angularDamping=0.5;
ballBody=myWorld->CreateBody(&ballBodyDef);
b2CircleDef ballShapeDef;
ballShapeDef.radius=
S2W(self.ballView.bounds.size.width/2);
ballShapeDef.density= 0.1f;
ballShapeDef.friction= 0.3f;
ballShapeDef.restitution = 0.2f;
ballBody->CreateShape(&ballShapeDef);
ballBody->SetMassFromShapes();
To find out where our body is at any time we can query the body for its position, velocity, rotation and other attributes.
The code below updates our ball view to the position of the ball in the physics world:
// find out where our ball should be
b2Vec2 position = ballBody->GetPosition();
ballView.center=CGPointMake(W2S(position.x),
W2S(position.y));
If you run up Demo 1 you can see this code in action. You can reset the balls location by touching the screen.
Obviousy we need some walls around the world to stop the ball escaping and the ball should be bouncing off the planets. We’ll create static shapes for these objects. To create a static shape, just give it no mass - adding walls and the planets gives us Demo 2.
The next step is to wire up the flippers. For this we need to create some joints. Box2D offers us a number of options for joining objects together:
<img style=”display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 335px;” src=”http://4.bp.blogspot.com/_jO69rW-vio4/S2XXgVjbJcI/AAAAAAAAB-E/sN1oViv_lwY/s400/Screen+shot+2010-01-31+at+14.17.47.png” border=”0” alt=”“id=”BLOGGER_PHOTO_ID_5432985476033881538” /> For our purposed we’ll be using a revolute joint to connect our flipper to the shoulders of the pinball table.
b2RevoluteJointDef jointDefLeftPaddle;
jointDefLeftPaddle.Initialize(leftSlopeBody, leftPaddleBody,
b2Vec2(S2W(self.leftPaddleView.frame.origin.x), S2W(self.leftPaddleView.frame.origin.y)));
jointDefLeftPaddle.lowerAngle = -0.25*b2_pi;
jointDefLeftPaddle.upperAngle = 0.25*b2_pi;
jointDefLeftPaddle.enableLimit = true;
myWorld->CreateJoint(&jointDefLeftPaddle);
To make our flippers move we’ll apply torque (rotational force) to the body when the user touches the screen. We’ll also play a sound at the same time.
if(touchPos.y>400 && touchPos.x<90) {
leftPaddleBody->ApplyTorque(-500);
AudioServicesPlaySystemSound(flipperSound);
} else if(touchPos.y>400 && touchPos.x>210) {
rightPaddleBody->ApplyTorque(500);
AudioServicesPlaySystemSound(flipperSound);
}
Demo 3 shows this all working. We’ve now got an almost complete game. All that’s left it to detect when the ball collides with the planets and other things.
Box2D create contacts to manage collisions between shapes. These have:
- Contact point - where the shapes touch
- Contact normal - points from shape1 to shape2
- Contact seperation - opposite of penetration
- Normal Force - collision intensity
- Tangent Force - friction force
To listen for contacts we need to create a C++ class that implements the b2ContactListener interface. This has the following methods:
- Add - a contact has been added
- Persist - a contact persisted for more than one timestep
- Remove - contact removed
- Result - computed results for contact
One thing to be aware of is that within a single timestep you might receive multiple notifications of contacts between objects. In my code I simply set a flag to indicate what the ball has collided with and process it after the world step has completed.
class ContactListener : public b2ContactListener {
PinBallViewController *pinBallViewController;
public:
ContactListener(PinBallViewController *controller) : pinBallViewController(controller) {}
void Result(const b2ContactResult* point) {
b2Body *body1=point->shape1->GetBody();
b2Body *body2=point->shape2->GetBody();
[pinBallViewController contactBetween:body1 andBody:body2 withForce:point->normalImpulse];
}
};
contactListener=new ContactListener(self);
myWorld->SetContactListener(contactListener);
With this in place we can detect when the ball hits the planets, the walls and the flippers. The complete demo (Demo4) shows this all working. We’ve got a basic working game in a couple of 100 lines of code!
Feel free to reuse any of the code in your own projects.