How To Create A Breakout Game with Box2D and Cocos2D 2.X Tutorial: Part 1
Update 1/9/2013: Fully updated for Cocos2D 2.1-beta4 (original post by Ray Wenderlich, update by Brian Broom). Box2D is a powerful physics library that comes with the Cocos2D game programming library for the iPhone. There’s a lot you can do with it, and a great way to start learning about how it works is to create […] By Brian Broom.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How To Create A Breakout Game with Box2D and Cocos2D 2.X Tutorial: Part 1
20 mins
Moving The Paddle
So let’s get moving! Moving the paddle is going to require touches, so enable touches in your init method:
self.touchEnabled = YES;
Then add the following member variable to your HelloWorld class in HelloWorldLayer.h:
b2MouseJoint *_mouseJoint;
Now let’s implement the touch methods! Let’s start with ccTouchesBegan:
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (_mouseJoint != NULL) return;
UITouch *myTouch = [touches anyObject];
CGPoint location = [myTouch locationInView:[myTouch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
b2Vec2 locationWorld = b2Vec2(location.x/PTM_RATIO, location.y/PTM_RATIO);
if (_paddleFixture->TestPoint(locationWorld)) {
b2MouseJointDef md;
md.bodyA = _groundBody;
md.bodyB = _paddleBody;
md.target = locationWorld;
md.collideConnected = true;
md.maxForce = 1000.0f * _paddleBody->GetMass();
_mouseJoint = (b2MouseJoint *)_world->CreateJoint(&md);
_paddleBody->SetAwake(true);
}
}
Wow, a lot of new stuff in here. Let’s discuss it bit by bit.
First, we convert the touch location to our Cocos2D coordinates (convertToGL) and then to our Box2D coordinates (locationWorld).
Then we use a method on our paddle fixture object that we’ve stored away to see if the touch point is within the fixture.
If it is, we create something called a “mouse joint.” In Box2D, a mouse joint is used to make a body move toward a specified point – in this case where the user is tapping.
When you set up a mouse joint, you have to give it two bodies. The first isn’t actually used, but the convention is to use the ground body. The second is the body you want to move – in our case the paddle.
Then you specify where you want the target to move – in our case where the user is tapping.
Then you tell Box2D that when bodyA and bodyB collide, treat it as a collision, rather than ignoring it. This is very important. When I was trying to get this working, I didn’t have this set, so when I was moving the paddle with my mouse it wouldn’t collide with the edges of the screen, and my paddle would fly off screen sometimes! This was very confusing and frustrating until I discovered this simple way to fix it :]
You then specify the max force with which to move the body. If you reduce this amount, the body will react more slowly to your mouse movements (which may be what you want sometimes!). But here we want the paddle to respond rather quickly to movements.
Finally we add the joint to the world, and store away the pointer for future reference. We also set the body to awake. We need to do this because if the body is asleep and we don’t awaken it, it won’t respond to the movements!
Ok, next let’s add the ccTouchesMoved method:
-(void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (_mouseJoint == NULL) return;
UITouch *myTouch = [touches anyObject];
CGPoint location = [myTouch locationInView:[myTouch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
b2Vec2 locationWorld = b2Vec2(location.x/PTM_RATIO, location.y/PTM_RATIO);
_mouseJoint->SetTarget(locationWorld);
}
The beginning of this method is the same as ccTouchesBegan – we get the location of the touch in Box2D coordinates. The only thing we do here is update the target of the mouse joint (i.e. where we want the body to move) to be the current location of the touch.
Let’s wrap up by adding ccTouchesEnded and ccTouchesCancelled:
-(void)ccTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
if (_mouseJoint) {
_world->DestroyJoint(_mouseJoint);
_mouseJoint = NULL;
}
}
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (_mouseJoint) {
_world->DestroyJoint(_mouseJoint);
_mouseJoint = NULL;
}
}
All we do in these methods is destroy the mouse joint because when the touches end, we’re done moving the object.
Give it a compile and run, and you should be able to move the paddle all around the screen to bounce the ball!
Pretty cool… but wait a minute, this isn’t breakout, we shouldn’t be able to move the paddle anywhere we want, we should just be able to move it back and forth!
Restricting Movement of the Paddle
We can easily restrict movement of the paddle by adding another joint into the world called a prismatic joint. This lets us restrict the movement of one body to another along a specified axis.
So we can use this to restrict the movement of the paddle relative to the ground to only be able to move along the x-axis.
Let’s give this a shot in code. Add this to your init method:
// Restrict paddle along the x axis
b2PrismaticJointDef jointDef;
b2Vec2 worldAxis(1.0f, 0.0f);
jointDef.collideConnected = true;
jointDef.Initialize(_paddleBody, _groundBody,
_paddleBody->GetWorldCenter(), worldAxis);
_world->CreateJoint(&jointDef);
The first thing we do is specify the axis to be a vector along the x axis, but not at all along the y axis. We then specify the ever important collideConnected value so our paddle will correctly bounce against the edge of the screen rather than flying into never-never land.
We then initialize the joint specifying the paddle and the ground body and create the joint!
Give it a compile and run and now you should only be able to move the paddle back and forth instead of anywhere you want:
Finishing Touches
Now, as you’ve been playing around with this so far you may have noticed that sometimes the ball can get super-fast or super slow, depending on how you hit it with the paddle.
Update: The first time I tried to fix this, I tried to adjust the velocity of the ball directly by calling SetLinearVelocity. However, as Steve Oldmeadow also pointed out (thanks Steve!), this is a bad idea as it messes up the collision simulations, and it’s better to indirectly affect the velocity by increasing the linear damping. So that’s what we’ll do!
Add the following code to the tick method, after getting the user data:
// if ball is going too fast, turn on damping
if (sprite.tag == 1) {
static int maxSpeed = 10;
b2Vec2 velocity = b->GetLinearVelocity();
float32 speed = velocity.Length();
if (speed > maxSpeed) {
b->SetLinearDamping(0.5);
} else if (speed < maxSpeed) {
b->SetLinearDamping(0.0);
}
}
Here I check the tag of the sprite to see if it’s the tag for the ball object. If it is, I check the velocity and if it’s too too large, I increase the linear damping so it will eventually slow down.
If you compile and run you should see the ball goes back to a normal rate when the speed increases too much.