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.

Leave a rating/review
Save for later
Share

Simple Breakout Game Screenshot

Simple Breakout Game Screenshot

Simple Breakout Game Screenshot

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!

We left off with a box that bounces around the screen and a paddle we could move with our fingers. Let’s start adding in some game logic by making the player lose if the ball hits the bottom of the screen!

Box2D and Collisions

To find out when a fixture collides with another fixture in Box2D, we need to register a contact listener. A contact listener is a C++ object that we give Box2D, and it will call methods on that object to let us know when two objects begin to touch and stop touching.

The trick to a contact listener, however, is according to the Box2D User Manual, you cannot perform any operation that alters game physics within the callback. Since this is something we will probably want to do (such as destroy an object when two objects collide), instead we will just keep references to the collisions so we can deal with them later.

Another tricky bit is we can’t just store references to the contact points that are sent to the listener, because they are reused by Box2D. So we have to store copies of them instead.

Ok enough talk, let’s try this out for ourselves!

When We’ve Hit Rock Bottom

Note that in this section we’re going to be using some C++ and the standard template library (STL) a bit. If you are unfamiliar with C++ or the STL, don’t worry about it too much – you can just copy and paste the code, it is general purpose and should work in your projects as well.

Ok. From the menu, add a new file (File\New File), click “Cocoa Touch” on the left, and choose “Objective-C class”, and click Next. Name your class MyContactListener, verifying that NSObject is selected in the “Subclass of” field, then click Next. Make sure “Box2DBreakout” is checked in the list of targets at the bottom of the window, and click Create.

Click to select MyContactListener.m and click again to rename the file to MyContactListener.mm. This is because we are actually creating a C++ class in this file, and the convention when you are using C++ in a file is to have the file end with mm.

Then replace the contents of MyContactListener.h with the following file:

#import "Box2D.h"
#import <vector>
#import <algorithm>

struct MyContact {
    b2Fixture *fixtureA;
    b2Fixture *fixtureB;
    bool operator==(const MyContact& other) const
    {
        return (fixtureA == other.fixtureA) && (fixtureB == other.fixtureB);
    }
};

class MyContactListener : public b2ContactListener {

public:
    std::vector<MyContact>_contacts;
    
    MyContactListener();
    ~MyContactListener();
    
    virtual void BeginContact(b2Contact* contact);
    virtual void EndContact(b2Contact* contact);
    virtual void PreSolve(b2Contact* contact, const b2Manifold* oldManifold);    
    virtual void PostSolve(b2Contact* contact, const b2ContactImpulse* impulse);
    
};

Here we define the structure that we will use to keep track of the data we’re interested in from the contact notifications. Again, we need to store a copy because the contact points passed in are reused. note we have to declare an equality operator here, because we’re going to use a the find() method to look for matching objects in the vector, which requires this method.

After that we declare our contact listener class, which derives from b2ContactListener. We just declare the methods we need to implement, as well as a STL vector that we will use to buffer our contact points.

Now replace the contents of MyContactListener.mm with the following:

#import "MyContactListener.h"

MyContactListener::MyContactListener() : _contacts() {
}

MyContactListener::~MyContactListener() {
}

void MyContactListener::BeginContact(b2Contact* contact) {
    // We need to copy out the data because the b2Contact passed in
    // is reused.
    MyContact myContact = { contact->GetFixtureA(), contact->GetFixtureB() };
    _contacts.push_back(myContact);
}

void MyContactListener::EndContact(b2Contact* contact) {
    MyContact myContact = { contact->GetFixtureA(), contact->GetFixtureB() };
    std::vector<MyContact>::iterator pos;
    pos = std::find(_contacts.begin(), _contacts.end(), myContact);
    if (pos != _contacts.end()) {
        _contacts.erase(pos);
    }
}

void MyContactListener::PreSolve(b2Contact* contact, 
  const b2Manifold* oldManifold) {
}

void MyContactListener::PostSolve(b2Contact* contact, 
  const b2ContactImpulse* impulse) {
}

We initialize our vector in the constructor. Then the only two methods we actually implement are BeginContact and EndContact. In BeginContact we make a copy of the fixtures that just collided, and store them in our vector. In EndContact, we look to see if the contact point is in our vector and remove it if so.

Ok, now let’s put this to use. Switch over to HelloWorldLayer.h and make the following modifications:

// Add to top of file
#import "MyContactListener.h"

// Add inside @interface
MyContactListener *_contactListener;

Then add the following code to your init method:

// Create contact listener
_contactListener = new MyContactListener();
_world->SetContactListener(_contactListener);

Here we create our contact listener object, and call a method on the world object to set the contact listener.

Next add the cleanup code to dealloc before we forget:

delete _contactListener;

And finally add the following code to the bottom of your tick method:

//check contacts
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!");
    }
}

This iterates through all of the buffered contact points, and checks to see if any of them are a match between the ball and the bottom of the screen. For now, we just log this out with a NSLog message because it’s time to check if it’s working!

So run your project, and make sure your console is visible by selecting View -> Debug Area -> Activate Console in the menu. Whenever the ball intersects the bottom you should see a message in your log that reads “Ball hit bottom!”

Adding a game over scene

Add the GameOverLayer.h and GameOverLayer.m files that we developed in the how to make a simple game with Cocos2D tutorial. Note that you’ll have to rename GameOverLayer.m to GameOverLayer.mm since we’re dealing with C++ code now or you will get compilation errors.

Then add the import to the top of your HelloWorldLayer.mm file:

#import "GameOverLayer.h"

Then replace the NSLog statement with the following code:

CCScene *gameOverScene = [GameOverLayer sceneWithWon:NO];
[[CCDirector sharedDirector] replaceScene:gameOverScene];

Allright, we’re getting somewhere! But what fun is a game where you can’t win?

Brian Broom

Contributors

Brian Broom

Author

Over 300 content creators. Join our team.