How To Create A Breakout Game with Box2D and Cocos2D 2.X Tutorial: Part 2
Update 1/9/2013: Fully updated for Cocos2D 2.1-beta4 (original post by Ray Wenderlich, update by Brian Broom). This is the second and final part of a tutorial on how to create a simple breakout game using the Box2D physics library that comes with Cocos2D. If you haven’t already, make sure you go through part 1 first! […] 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 2
15 mins
Adding some blocks
Download a copy of a block image and retina version I made and drag it to the Resources folder of your project, making sure “Copy items into destination group’s folder (if needed)” is checked.
Then add the following code to your init method:
for(int i = 0; i < 4; i++) {
static int padding=20;
// Create block and add it to the layer
CCSprite *block = [CCSprite spriteWithFile:@"block.png"];
int xOffset = padding+block.contentSize.width/2+
((block.contentSize.width+padding)*i);
block.position = ccp(xOffset, 250);
block.tag = 2;
[self addChild:block];
// Create block body
b2BodyDef blockBodyDef;
blockBodyDef.type = b2_dynamicBody;
blockBodyDef.position.Set(xOffset/PTM_RATIO, 250/PTM_RATIO);
blockBodyDef.userData = block;
b2Body *blockBody = _world->CreateBody(&blockBodyDef);
// Create block shape
b2PolygonShape blockShape;
blockShape.SetAsBox(block.contentSize.width/PTM_RATIO/2,
block.contentSize.height/PTM_RATIO/2);
// Create shape definition and add to body
b2FixtureDef blockShapeDef;
blockShapeDef.shape = &blockShape;
blockShapeDef.density = 10.0;
blockShapeDef.friction = 0.0;
blockShapeDef.restitution = 0.1f;
blockBody->CreateFixture(&blockShapeDef);
}
You should understand this code pretty well by now. We create a body just the same way we did for the paddle, except this time we do it in a loop so we can easily create four blocks along the top. Also notice that we set the tag on the block sprite to 2, for future reference.
Compile and run this code, and you should now have blocks you can mess around with your ball!
Destroying the Blocks
To be a true breakout game, we need to destroy the blocks when the ball intersects them. Well we’ve already added the code to keep track of collisions, so all we need to do is modify the tick method!
Modify the code in your tick method after the “check contacts” comment to be the following:
//check contacts
std::vector<b2Body *>toDestroy;
std::vector<MyContact>::iterator pos;
for (pos=_contactListener->_contacts.begin();
pos != _contactListener->_contacts.end(); ++pos) {
MyContact contact = *pos;
if ((contact.fixtureA == _bottomFixture && contact.fixtureB == _ballFixture) ||
(contact.fixtureA == _ballFixture && contact.fixtureB == _bottomFixture)) {
//NSLog(@"Ball hit bottom!");
CCScene *gameOverScene = [GameOverLayer sceneWithWon:NO];
[[CCDirector sharedDirector] replaceScene:gameOverScene];
}
b2Body *bodyA = contact.fixtureA->GetBody();
b2Body *bodyB = contact.fixtureB->GetBody();
if (bodyA->GetUserData() != NULL && bodyB->GetUserData() != NULL) {
CCSprite *spriteA = (CCSprite *) bodyA->GetUserData();
CCSprite *spriteB = (CCSprite *) bodyB->GetUserData();
//Sprite A = ball, Sprite B = Block
if (spriteA.tag == 1 && spriteB.tag == 2) {
if (std::find(toDestroy.begin(), toDestroy.end(), bodyB) == toDestroy.end()) {
toDestroy.push_back(bodyB);
}
}
//Sprite A = block, Sprite B = ball
else if (spriteA.tag == 2 && spriteB.tag == 1) {
if (std::find(toDestroy.begin(), toDestroy.end(), bodyA) == toDestroy.end()) {
toDestroy.push_back(bodyA);
}
}
}
}
std::vector<b2Body *>::iterator pos2;
for (pos2 = toDestroy.begin(); pos2 != toDestroy.end(); ++pos2) {
b2Body *body = *pos2;
if (body->GetUserData() != NULL) {
CCSprite *sprite = (CCSprite *) body->GetUserData();
[self removeChild:sprite cleanup:YES];
}
_world->DestroyBody(body);
}
Ok, let’s explain this. We go through the contact points again, but this time after we check for collisions between the ball and the bottom of the screen, we take a look at the bodies that are colliding. We can get to the bodies by calling the GetBody() method on the fixtures.
Once we have the bodies, we check to see if they have user data. If they do, we cast them to sprites – because we know that’s what we’ve set the user data to.
Then we look to see what sprites are colliding based on their tags. If a sprite is intersecting with a block, we add the block to a list of objects to destroy.
Note that we add it to a list to destroy rather than destroying the body right away. This is because if we destroy the body right away, the world will clean up a lot of pointers leaving us with garbage data in our contact listener. Also note that we only should add it to the list if it isn’t there already!
Finally, we go through the list of bodies we want to delete. Note that we not only have to destroy the body from Box2D’s world, we also have to remove the sprite object from our Cocos2D scene.
Give it a compile and run, and you should now be able to destroy bricks! Yay!
Winning the Game
Next we need to add some logic in to let the user actually win the game. Modify the beginning of your tick method to read as follows:
- (void)tick:(ccTime) dt {
bool blockFound = false;
_world->Step(dt, 10, 10);
for(b2Body *b = _world->GetBodyList(); b; b=b->GetNext()) {
if (b->GetUserData() != NULL) {
CCSprite *sprite = (CCSprite *)b->GetUserData();
if (sprite.tag == 2) {
blockFound = true;
}
//...
All we’re doing here is looking to see if we ever come across a block while we’re iterating through the objects in the scene – if we do find one we set the blockFound variable to true – otherwise it is false.
Then add the following code at the end of the function:
if (!blockFound) {
CCScene *gameOverScene = [GameOverLayer sceneWithWon:YES];
[[CCDirector sharedDirector] replaceScene:gameOverScene];
}
Here we just display a game over scene if no blocks were found. Give it a compile and run, and see if you can win the game!
Finishing Touches
The game is quite cool, but we need some sound of course! You can download the awesome background music I made and a cool blip sound I made to use. As usual, drag them to your resources folder once you’ve downloaded them.
Note: When I dragged the sound files into the project, they were not automatically added to the build target, which crashed the app when I ran it. When you drag the files into your project, there is a checkbox at the bottom of the window for the files to be included in the target. You can check this setting by selecting the audio file (or any file, actually) and looking in the File Inspector in the right sidebar. There is a section called “Target Membership”, and there is a checkbox next to your app name. Check the box if it is not checked.
By the way – I made the sound effect with an awesome program called cfxr that one of our commenters – Indy – pointed out. Thanks Indy this program pwns!
Anyway – once you’ve added the files to your project, add the following to the top of HelloWorldScene.mm:
#import "SimpleAudioEngine.h"
And the following to your init method:
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"background-music-aac.caf"];
And finally the following at the end of your tick method:
if (toDestroy.size() > 0) {
[[SimpleAudioEngine sharedEngine] playEffect:@"blip.caf"];
}
And there you have it – your own simple breakout game with Box2D physics!