Chapter 12

Physics Engines

Physics engines are what drive popular iOS games like Angry Birds, Stick Golf, Jelly Car, and Stair Dismount. They allow you to create a world that feels more dynamic and lifelike.

Cocos2d is distributed with two physics engines: Box2D and Chipmunk. Both are designed to work only in two dimensions, so they're a perfect fit for cocos2d.

In this chapter, you'll learn the basics of both physics engines, and along the way you'll probably come to appreciate one more than the other. I'll briefly explain the differences between the two physics engines, but for the most part it's a choice based on personal preference.

If you've never worked with a physics engine before, I'll also give you a quick introduction to their basic concepts and key elements.

Basic Concepts of Physics Engines

You can think of a physics engine as an animation system for game objects. Of course, it's up to the game developer to connect and synchronize game objects like sprites with the physics objects, called rigid bodies. They are called that because physics engines animate them as if they were stiff, nondeformable objects. This simplification allows physics engines to calculate a large number of bodies.

There are generally two types of bodies: dynamic (moving) and static (immovable) objects. The differentiation is important because static bodies never move—and should never be moved—and the physics engine can rely on certain optimizations based on the fact that static bodies never collide with each other.

Dynamic bodies, on the other hand, collide with each other and with static bodies. They also have at least three defining parameters in addition to their position and rotation. One is density, or mass—in other words, a measure of how heavy an object is. Then there is friction—how resistant or slippery the dynamic body is with respect to moving over surfaces. Finally, there is restitution, which determines the bounciness of the object. While impossible in the real world, physics engines can create dynamic bodies that will never lose momentum as they bounce, or even gain speed every time they bounce off of some other body.

Both dynamic and static bodies have one or more shapes that determine the area the body encompasses. Most often the shape is a circle or a rectangle, but it can also be a polygon, a number of vertices forming any complex shape, or merely a straight line. The shapes of a body determine where other bodies and their shapes collide. And in turn, each collision generates contact points—the points where the two bodies' shapes intersect. These contact points can be used to play particle effects or add scratch marks at exactly the places where the bodies have collided.

Dynamic bodies are animated by the physics engine through applying forces, impulses, and torque instead of setting their position and rotation directly. Modifying position and rotation directly is advised against, because physics engines make certain predictions that no longer hold true if you manually reposition bodies.

Finally, bodies can be connected together by using a selection of joints, which limit the movement of connected bodies in various ways. Some joints may have motors, which for example can act as the drive wheel of a car or as friction for the joint so that the joint tries to snap back to its original position.

Limitations of Physics Engines

Physics engines have their limits. They have to take shortcuts because the real world is prohibitively complex to simulate. The use of rigid bodies is such an example. In some extreme cases, physics engines may not be able to catch every collision—for example, when bodies are moving very fast, in which case they can tunnel through each other. While this has been proven to happen in quantum physics, real-world objects that we can actually see with our own eyes have yet to show that effect.

Rigid bodies can sometimes penetrate each other and get stuck, especially if they are constrained by joints that hold two or more bodies together and limit their range of motion. This can lead to undesirable trembling motions of the bodies as they struggle to move apart while keeping their joint constraints satisfied. You may have seen this even in modern games; for example, the ragdolls of dead actors in a first-person shooter game can sometimes be seen in very unnatural body positions or limbs not coming to rest at all. Such events continue to amaze and amuse players.

And of course there can be game-play issues. With rigid bodies, you never know what will happen given enough players interacting with them. Eventually some players may manage to block themselves and trap themselves in a dead-end situation, or they may figure out how to exploit the physics simulation to be able to move to areas they shouldn't be able to reach.

The Showdown: Box2D vs. Chipmunk

Cocos2d is distributed with two physics engines: Box2D and Chipmunk. How should you choose between them?

In a lot of cases, this boils down to a matter of taste. Most developers argue along the lines of the programming language in which the physics engines are implemented: Box2D is written entirely in C++, while Chipmunk is written in C.

You may favor Box2D over Chipmunk simply because of its C++ interface. Being written in C++ has the added advantage that it integrates better with the likewise object-oriented Objective-C language. You may also appreciate that Box2D uses fully written-out words throughout, as opposed to the many one-letter abbreviations common in Chipmunk. In addition, Box2D makes use of operator overloading so that you can, for example, add two vectors simply by writing the following:

b2Vec2 newVec = vec1 + vec2;

Box2D has a few features that Chipmunk doesn't offer. For example, it has a solution for fast-moving objects (bullets) that solves the tunneling problem I mentioned earlier.

If you're not very familiar with C++, you may find the steep learning curve of the C++ language daunting. To that end, the Chipmunk physics engine may be more welcoming to you if you're more familiar with C language syntax or prefer a lightweight implementation of a physics engine that is easier to pick up and learn. Its being part of the cocos2d distribution for many months longer than Box2D has also spawned more tutorials and forum posts about Chipmunk, although Box2D tutorials are catching on.

One warning ahead of time: Chipmunk uses C structures, which expose internal fields. If you're experimenting and don't know what certain fields are used for and they're not documented, that means you should not change them—because they are used only internally.

There is also the popular Chipmunk SpaceManager, which adds an easy-to-use Objective-C interface to Chipmunk. SpaceManager also makes it easy to attach cocos2d sprites to bodies and adds debug drawing, among other things. You can download Chipmunk SpaceManager here: http://code.google.com/p/chipmunk-spacemanager.

In terms of functionality, you can safely choose either engine. Unless your game relies on one particular feature that one physics engine has and the other doesn't, you can use either one to great effect. Especially if you have no familiarity with either physics engine, feel free to choose the one that appeals to you more based on the language and coding style.

I will now introduce you to the basics of both physics engines for the rest of this chapter so that you can decide for yourself which one appeals to you more. In the next chapter, you'll learn how to build a playable pinball game with bumpers, flippers, and lanes built with Box2D and the VertexHelper tool.

Box2D

The Box2D physics engine is written in C++. It was developed by Erin Catto, who has given presentations about physics simulations at every Game Developers Conference (GDC) since 2005. It was his GDC 2006 presentation that eventually led to the public release of Box2D in September 2007. It has been in active development ever since.

Because of its popularity, the Box2D physics engine is distributed with cocos2d. You can create a new project using Box2D by choosing the cocos2d Box2D application template from Xcode's File images New Project dialog. This project template adds the necessary Box2D source files to the project and provides a test project in which you can add boxes that bounce off each other, as shown in Figure 12–1. They also fall according to gravity, depending on how you're holding your device.

images

Figure 12–1. The PhysicsBox2D example project

CAUTION: Because the Box2D physics engine is written in C++, you have to use the file extension .mm instead of .m for all your project's implementation files. This tells Xcode to treat the implementation file's source code as either Objective-C++ or C++ code. With the .m file extension, Xcode will compile the code as Objective-C and C code and won't understand Box2D's C++ code, which will result in numerous compile errors for every line of code that uses or makes a reference to Box2D. So if you're getting a lot of errors, check that your implementation files all end in .mm, and if not, rename them.

Documentation for Box2D is available in two places. First, you can read the Box2D manual online at www.box2d.org/manual.html, which introduces you to common concepts and shows example code. The Box2D API reference is distributed with Box2D itself, which you can download at http://code.google.com/p/box2d. You can also find the Box2D API reference in the Box2D version distributed with the book's source code. The API reference is in the folder /Physics Engine Libraries/Box2D_v2.1.2/Box2D/Documentation/API — to view it, locate and open the file index.html in that folder.

If you like Box2D, you should also consider donating to the project; you can do so via the Donate button on its home page: www.box2d.org.

The World According to Box2D

Because the example project provided by cocos2d is quite complex, I decided to break it down into smaller pieces and re-create the example project step by step but not without adding some extras and variations.

Listing 12–1 shows the HelloWorldScene header file from the PhysicsBox2D01 project.

Listing 12–1. The Box2D Hello World Interface

#import "cocos2d.h"
#import "Box2D.h"
#import "GLES-Render.h"

enum
{
    kTagBatchNode,
};

@interface HelloWorld : CCLayer
{
    b2World* world;
}

+(id) scene;

@end

It's fairly standard, except that it includes the Box2D.h header file and adds a member variable of type b2World. This is the physics world—think of it as the container class that will store and update all physics bodies.

The Box2D world is initialized by creating a new b2World object in the HelloWorldScene's init method, as shown in Listing 12–2.

Listing 12–2. Initializing the Box2D World

b2Vec2 gravity = b2Vec2(0.0f, -10.0f);
bool allowBodiesToSleep = true;
world = new b2World(gravity, allowBodiesToSleep);

Remember that Box2D is written in C++. To instantiate one of Box2D's classes, you have to add the new keyword in front of the class's name. If Box2D were written in Objective-C, the equivalent line might look like this:

world = [[b2World alloc] initWithGravity:gravity allowSleep:allowBodiesToSleep];

In other words, the new keyword in C++ is equivalent to sending the alloc message to an Objective-C class followed by an init message. That of course means you also have to deallocate the Box2D world. In C++ this is done using the delete keyword:

-(void) dealloc
{

    delete world;
    [super dealloc];
}

The Box2D world in Listing 12–2 is initialized with an initial gravity vector and a flag that determines whether the dynamic bodies are allowed to fall asleep.

Sleeping bodies? It's a trick that allows the physics simulation to quickly skip over objects that do not need processing. A dynamic body goes to sleep when the forces applied to it have been below a threshold for a certain amount of time. In other words, if the dynamic body is moving and rotating very slowly or not at all, the physics engine will flag it as sleeping and won't apply forces to it anymore—that is, unless an impulse or force applied to the body is strong enough to make the body move or rotate again. This trick allows the physics engine to save time by not processing the bodies that are at rest. Unless all of your game's dynamic bodies are in constant motion, you should enable this feature by setting the allowBodiesToSleep variable to true, as in Listing 12–2.

The gravity passed to Box2D is a b2Vec2struct type. It's essentially the same as a CGPoint, because it stores x and y float values. In this case, and fortunately for us in the real world too, gravity is a constant force. The 0, –10 vector is constantly applied to all dynamic bodies, making them fall down, which in this case means toward the bottom of the screen.

Restricting Movement to the Screen

World setup, check. What next? Well, we should limit the movement of the Box2D bodies to within the visible screen area. For that, we'll need a static body. The simplest way to create a static body is by using the world's CreateBody method and an empty body definition:

// Define the static container body, which will provide the collisions at screen borders
b2BodyDef containerBodyDef;
b2Body* containerBody = world->CreateBody(&containerBodyDef);

Bodies are always created through the world's CreateBody method. This ensures that the body's memory is correctly allocated and freed. The b2BodyDef is a struct that holds all the data needed to create a body, such as position and the body type. By default, an empty body definition creates a static body at position 0, 0.

NOTE: The &containerBodyDef variable is passed with a leading & (ampersand) character to the CreateBody method. That's C++ for “Give me the memory address of containerBodyDef.” If you look at the definition of the CreateBody method, it requires a pointer passed to it: b2World::CreateBody(const b2BodyDef *def);. Since pointers store a memory address, you can get that address of a nonpointer variable by prefixing it with the ampersand character.

The body itself won't do anything. To make it enclose the screen area, you'll have to create a shape with four sides:

// Create the screen box sides by using a polygon assigning each side individually
b2PolygonShape screenBoxShape;
int density = 0;

// Bottom
screenBoxShape.SetAsEdge(lowerLeftCorner, lowerRightCorner);
containerBody->CreateFixture(&screenBoxShape, density);

// Top
screenBoxShape.SetAsEdge(upperLeftCorner, upperRightCorner);
containerBody->CreateFixture(&screenBoxShape, density);

// Left side
screenBoxShape.SetAsEdge(upperLeftCorner, lowerLeftCorner);
containerBody->CreateFixture(&screenBoxShape, density);

// Right side
screenBoxShape.SetAsEdge(upperRightCorner, lowerRightCorner);
containerBody->CreateFixture(&screenBoxShape, density);

You may notice the missing declarations for the corner variable names. I'll get to them in a moment. First I'd like you to focus on how the b2PolygonShape screenBoxShape variable is reused. Each SetAsEdge method call is followed by a call to containerBody->CreateFixture(), which uses the -> operator to denote that containerBody is a C-style pointer and passes &screenBoxShape. Since Box2D makes a copy of screenBoxShape, you can safely reuse the same shape to create all four sides enclosing the screen area without modifying or overriding the previous lines. Since the body is a static body (the default setting for Box2D bodies), density doesn't matter and is set to 0.

NOTE: The b2PolygonShape class has a SetAsBox method that looks like it might make the definition of the screen area easier by simply providing the screen's width and height. However, that would make the inside of the body a solid object, and any dynamic body added to the screen would actually be contained inside the solid shape. This would make the dynamic bodies try to move away from the collision, possibly at rapid speeds. The sides need to be created separately in order to make only the sides of the screen solid.

Now we'll move on to the missing variable declarations. Notice that the screen width and height is divided by a PTM_RATIO constant to convert them from pixels to meters:

// For the ground body we'll need these values
CGSize screenSize = [CCDirector sharedDirector].winSize;
float widthInMeters = screenSize.width / PTM_RATIO;
float heightInMeters = screenSize.height / PTM_RATIO;
b2Vec2 lowerLeftCorner = b2Vec2(0, 0);
b2Vec2 lowerRightCorner = b2Vec2(widthInMeters, 0);
b2Vec2 upperLeftCorner = b2Vec2(0, heightInMeters);

b2Vec2 upperRightCorner = b2Vec2(widthInMeters, heightInMeters);

Why meters, and what is PTM_RATIO? Box2D is optimized to work best with dimensions in the range of 0.1 to 10 meters. It is tuned for the metric system, so all distances are considered to be meters, all masses are in kilograms, and time is measured in—quite oddly—seconds. If you're not familiar with the meters, kilograms, and seconds (MKS) system, don't worry—you don't have to meticulously convert yards into meters and pounds into kilograms. The conversion to meters is just a way to keep the distance values for Box2D in the desirable range of 0.1 to 10, and the masses used by bodies do not resemble real-world masses anyway. The masses of bodies will often need to be tweaked by feel rather than by using realistic weights.

You should try to keep the dimensions of objects in your world as close to 1 meter as much as possible. That is not to say that you can't have objects that are smaller than 0.1 meters or larger than 10 meters, but you may run into glitches and strange behavior if you create relatively small or large bodies.

The PTM_RATIO is defined like this:

#define PTM_RATIO 32

It is used to define that 32 pixels on the screen equal 1 meter in Box2D. A box-shaped body that's 32 pixels wide and high will be 1 meter wide and high. A body that's 4×4 pixels in size will be 0.125×0.125 meters in Box2D, while a relatively huge object of 256×256 pixels will be 8×8 meters in Box2D. The PTM_RATIO allows you to scale the size of Box2D objects down to the dimensions within which Box2D works best, and a PTM_RATIO of 32 is a good compromise for a screen area that may be as large as 1024×768 pixels on the iPad.

Converting Points

Note that the b2Vec2struct is different from CGPoint, which means you cannot use a CGPoint where a b2Vec2 is required, and vice versa. In addition, Box2D points need to be converted to meters and back to pixels. To avoid making any mistakes, such as forgetting to convert from or to meters or simply making a typo and using the x coordinate twice, it's highly recommended to wrap this repetitive code into convenience methods like these:

-(b2Vec2) toMeters:(CGPoint)point
{
    return b2Vec2(point.x / PTM_RATIO, point.y / PTM_RATIO);
}

-(CGPoint) toPixels:(b2Vec2)vec
{
    return ccpMult(CGPointMake(vec.x, vec.y), PTM_RATIO);
}

This allows you to write the following code to easily convert between CGPoint and pixels to b2Vec2 and meters:

// Box2D coordinates to Cocos2D point coordinates
b2Vec2 vec = b2Vec2(200, 200);
CGPoint pointFromVec = [self toPixels:vec];

// Cocos2D point coordinates to Box2D coordinates                

CGPoint point = CGPointMake(100, 100);
b2Vec2 vecFromPoint = [self toMeters:point];

Adding Boxes to the Box2D World

With a static body containing the objects within screen boundaries, all that's missing is something to be kept within the screen boundaries. How about little boxes, then?

I've added David Gervais' orthogonal tileset image dg_grounds32.png to the Resources folder of the PhysicsBox2D01 project. The tiles are 32×32 pixels, so they'll make perfect 1×1-meter boxes. Listing 12–3 is the code in the init method that adds the texture and creates a couple boxes. It also schedules the update method, which is needed to update the box sprite positions, and it enables touch so that the user can tap the screen to create a new box.

Listing 12–3. Adding an Initial Set of Boxes

// Use the orthogonal tileset for the little boxes
CCSpriteBatchNode* batch = [CCSpriteBatchNodeimages
    batchNodeWithFile:@"dg_grounds32.png"
       capacity:150];

[self addChild:batch z:0 tag:kTagBatchNode];

// Add a few objects initially
for (int i = 0; i < 11; i++)
{
    [self addNewSpriteAt:CGPointMake(screenSize.width / 2, screenSize.height / 2)];
}

[self scheduleUpdate];
self.isTouchEnabled = YES;

The addNewSpriteAt method shown in Listing 12–4 is part of the cocos2d Box2D application template project but slightly modified to make use of all the tiles in the tileset.

Listing 12–4. Adding a New Dynamic Body with a Sprite

-(void) addNewSpriteAt:(CGPoint)pos
{
    CCSpriteBatchNode* batch =images
        (CCSpriteBatchNode*) [self getChildByTag:kTagBatchNode];

    int idx = CCRANDOM_0_1() * TILESET_COLUMNS;
    int idy = CCRANDOM_0_1() * TILESET_ROWS;
    CGRect tileRect = CGRectMake(TILESIZE * idx, TILESIZE * idy, TILESIZE, TILESIZE);
    CCSprite* sprite = [CCSprite spriteWithBatchNode:batch rect:tileRect];
    sprite.position = pos;
    [batch addChild:sprite];

    // Create a body definition and set it to be a dynamic body
    b2BodyDef bodyDef;
    bodyDef.type = b2_dynamicBody;
    bodyDef.position = [self toMeters:pos];
    bodyDef.userData = sprite;

    b2Body* body = world->CreateBody(&bodyDef);

    // Define a box shape and assign it to the body fixture
    b2PolygonShape dynamicBox;
    float tileInMeters = TILESIZE / PTM_RATIO;
    dynamicBox.SetAsBox(tileInMeters * 0.5f, tileInMeters * 0.5f);

    b2FixtureDef fixtureDef;
    fixtureDef.shape = &dynamicBox;        
    fixtureDef.density = 0.3f;
    fixtureDef.friction = 0.5f;
    fixtureDef.restitution = 0.6f;
    body->CreateFixture(&fixtureDef);
}

First, a sprite is created from the CCSpriteBatchNode by using CCSprite's spriteWithBatchNode initializer and supplying a CGRect that is 32×32 pixels in size, to randomly pick one of the tileset's tiles as the sprite's image.

Then a body is created, but this time the b2BodyDef type property is set to b2_dynamicBody, which makes it a dynamic body that can move around and collide with other dynamic bodies. The previously created sprite is assigned to the userData property. Later, when you are iterating over the bodies in the world, this allows you to quickly access the body's sprite.

The body's shape is a b2PolygonShape set to a box shape that is half a meter in size. The SetAsBox method creates a box shape that is twice the given width and height, so the coordinates need to be divided by 2—or, as in this case, multiplied by 0.5f to create a box shape whose sides are 1 meter wide and high.

The dynamic body also needs a fixture that contains the body's essential parameters—first and foremost the shape but also the density, friction, and restitution—which influence how the body moves and bounces around in the world. Consider the fixture to be a set of data used by bodies.

Connecting Sprites with Bodies

The box sprites won't follow their physics bodies automatically, and the bodies won't do anything unless you regularly call the Step method of the Box2D world. You then have to update the sprite positions by taking the body position and angle and assigning it to the sprite. This is done in the update method shown in Listing 12–5.

Listing 12–5. Updating Each Body's Sprite Position and Rotation

-(void) update:(ccTime)delta
{
    // Advance the physics world by one step, using fixed time steps
    float timeStep = 0.03f;
    int32 velocityIterations = 8;
    int32 positionIterations = 1;
    world->Step(timeStep, velocityIterations, positionIterations);

    for (b2Body* body = world->GetBodyList(); body != nil; body = body->GetNext())
    {

        CCSprite* sprite = (CCSprite*)body->GetUserData();
        if (sprite!= NULL)
        {
            sprite.position = [self toPixels:body->GetPosition()];
            float angle = body->GetAngle();
            sprite.rotation = CC_RADIANS_TO_DEGREES(angle) * -1;
        }
    }
}

The Box2D world is animated by regularly calling the Step method. It takes three parameters. The first is timeStep, which tells Box2D how much time has passed since the last step. It directly affects the distance that objects will move in this step. For games, it is not recommended to pass the delta time as timeStep, because the delta time fluctuates, and so the speed of the physics bodies will not be constant. This effect rears its ugly head when the device may be taking a tenth of a second to do background processing, such as sending or receiving an e-mail in the background. This can make all physics objects move large distances in the next frame. In a game, you'd rather have the game stop for a tenth of a second and then carry on where you left off. Without a fixed time step, the physics engine would try to cope with a short interruption by moving all objects based on the time difference. If the time difference is large, the objects will move a lot more in a single frame, and that can lead to them suddenly moving a large distance.

The second and third parameters to the Step method are the number of iterations. They determine the accuracy of the physics simulation and also the time it takes to calculate the movement of the bodies. It's a trade-off between speed and accuracy. In the case of position iterations, you can safely err on the side of speed and require only a single iteration—more position accuracy is not normally needed in games unless you experience objects not coming to rest, colliding in unnatural ways, or missing collision entirely. Velocity is more important, however; a good starting point for velocity iterations is eight. More than ten velocity iterations have no discernable effect in games, but just one to four iterations won't be enough to get a stable simulation. The fewer the velocity iterations, the more bumpily and restlessly the objects will behave. I encourage you to experiment with these values.

After the world has advanced one step, the for loop iterates over all of the world's bodies using the world->GetBodyList and body->GetNext methods. For each body, its user data is returned and cast to a CCSprite pointer. If it exists, the body's position is converted to pixels and assigned to the sprite's position so that the sprite moves along with the body. Likewise, the body's angle is obtained; because that measurement is in radians, it's converted to degrees using cocos2d's CC_RADIANS_TO_DEGREES method and multiplied by –1 to rotate the sprites in the same direction as the body.

Collision Detection

Box2D has a b2ContactListener class, which you are supposed to subclass if you want to receive collision callbacks. The following code refers to the PhysicsBox2D02 project.

Create a new class in Xcode and name it ContactListener. Then rename the implementation file to ContactListener.mm so it has the file extension .mm. Then you will be able to use both C++ and Objective-C code in the same file. Listing 12–6 shows the ContactListener header file.

Listing 12–6. The ContactListener Class's Interface

#import "Box2D.h"

class ContactListener : public b2ContactListener
{
private:
    void BeginContact(b2Contact* contact);
    void EndContact(b2Contact* contact);
};

It's a C++ class, so the class definition is a little different. Note the trailing semicolon after the last bracket. It's a common error to forget that semicolon. The BeginContact and EndContact methods are defined by Box2D and get called whenever there is a collision between two bodies.

In the implementation, I merely change the sprite's colors to magenta while the two bodies are in contact and set it back to white when they are no longer in contact, as shown in Listing 12–7.

Listing 12–7. Checking for the Beginning and Ending of Collisions

#import "ContactListener.h"
#import "cocos2d.h"

void ContactListener::BeginContact(b2Contact* contact)
{
    b2Body* bodyA = contact->GetFixtureA()->GetBody();
    b2Body* bodyB = contact->GetFixtureB()->GetBody();
    CCSprite* spriteA = (CCSprite*)bodyA->GetUserData();
    CCSprite* spriteB = (CCSprite*)bodyB->GetUserData();


    if (spriteA != NULL && spriteB != NULL)
    {
        spriteA.color = ccMAGENTA;
        spriteB.color = ccMAGENTA;
    }
}

void ContactListener::EndContact(b2Contact* contact)
{
    b2Body* bodyA = contact->GetFixtureA()->GetBody();
    b2Body* bodyB = contact->GetFixtureB()->GetBody();
    CCSprite* spriteA = (CCSprite*)bodyA->GetUserData();
    CCSprite* spriteB = (CCSprite*)bodyB->GetUserData();

    if (spriteA != NULL && spriteB != NULL)
    {
        spriteA.color = ccWHITE;
        spriteB.color = ccWHITE;
    }
}

b2Contact contains all the contact information, including two sets of everything suffixed with A and B. These are the two contacting bodies; no differentiation is made as to which is colliding with the other—they are both simply colliding with each other. If, for example, you have an enemy colliding with a player's bullet, you would want to damage the enemy, not the bullet. It is up to you to determine which is which. Also keep in mind that the contact methods may be called multiple times per frame, once for each contact pair.

It's a bit convoluted to get to the sprite from the contact through the fixture to the body and then get the user data from that. The Box2D API reference certainly helps you find your way through the hierarchy, and with a little experience this will become second nature.

To actually get the ContactListener connected with Box2D, you have to add it to the world. In HelloWorldScene, import the ContactListener.h header file and add a ContactListener* contactListener to the class as a member variable:

#import "cocos2d.h"
#import "Box2D.h"
#import "GLES-Render.h"

#import "ContactListener.h"

...

@interface HelloWorld : CCLayer
{
    b2World* world;
    ContactListener* contactListener;
}

...

@end

In the init method of HelloWorldScene, you can then create a new ContactListener instance and set it as the contact listener for the world:

contactListener = new ContactListener();
world->SetContactListener(contactListener);

What remains is to delete the contactListener in the dealloc method to free its memory:

-(void) dealloc
{
    delete contactListener;
    delete world;

    [super dealloc];
}

Now the boxes in the PhysicsBox2D02 project will be tinted purple whenever they touch other boxes.

Joint Venture

With joints, you can connect bodies together. The type of joint determines which way the connected bodies are connected. In this example method, I create four bodies total. Three are dynamic bodies connected to each other using a revolute joint, which keeps the bodies at the same distance but allows them to rotate 360 degrees around each other. If you find it hard to imagine how these objects might behave, you should try them in the PhysicsBox2D02 project. In this case, working code can explain it better than words or a static image could. The fourth body is a static body to which one of the dynamic bodies is also attached using a revolute joint.

-(void) addSomeJoinedBodies:(CGPoint)pos
{
    // Create a body definition and set it to be a dynamic body
    b2BodyDef bodyDef;
    bodyDef.type = b2_dynamicBody;

    // Position must be converted to meters
    bodyDef.position = [self toMeters:pos];
    bodyDef.position = bodyDef.position + b2Vec2(-1, -1);
    bodyDef.userData = [self addRandomSpriteAt:pos];
    b2Body* bodyA = world->CreateBody(&bodyDef);
    [self bodyCreateFixture:bodyA];

    bodyDef.position = [self toMeters:pos];
    bodyDef.userData = [self addRandomSpriteAt:pos];
    b2Body* bodyB = world->CreateBody(&bodyDef);
    [self bodyCreateFixture:bodyB];

    bodyDef.position = [self toMeters:pos];
    bodyDef.position = bodyDef.position + b2Vec2(1, 1);
    bodyDef.userData = [self addRandomSpriteAt:pos];
    b2Body* bodyC = world->CreateBody(&bodyDef);
    [self bodyCreateFixture:bodyC];

    // Create the revolute joints
    b2RevoluteJointDef jointDef;
    jointDef.Initialize(bodyA, bodyB, bodyB->GetWorldCenter());
    bodyA->GetWorld()->CreateJoint(&jointDef);

    jointDef.Initialize(bodyB, bodyC, bodyC->GetWorldCenter());
    bodyA->GetWorld()->CreateJoint(&jointDef);

    // Create an invisible static body and attach body A to it
    bodyDef.type = b2_staticBody;
    bodyDef.position = [self toMeters:pos];
    b2Body* staticBody = world->CreateBody(&bodyDef);

    jointDef.Initialize(staticBody, bodyA, bodyA->GetWorldCenter());
    bodyA->GetWorld()->CreateJoint(&jointDef);
}

b2BodyDef is reused for all bodies; only the position is modified for each body, and a random CCSprite is created and assigned as userData. The addRandomSpriteAt method contains the code that creates a sprite from the CCSpriteBatchNode, as discussed earlier. Since in the addSomeJoinedBodies method there are now several sprites needed, it made sense to refactor the creation of a sprite into the method addRandomSpriteAt.

b2RevoluteJointDef is filled with data by using the Initialize method providing two bodies to connect to each other and a coordinate where the joint is located. By using one body's GetWorldCenter coordinate, that body will be centered on the joint and allowed only to rotate around itself.

The joint is created by the CreateJoint method of the b2World class. Even though the HelloWorldScene class in the PhysicsBox2D02 project has a b2World member variable, I wanted to illustrate that you can also get the world through any body—it doesn't matter which one—by using the body's GetWorld method. This is good to know, because in the ContactListener discussed earlier, you do not have a b2World member variable, so you'll have to get the b2World pointer through one of the contact bodies.

Chipmunk

The Chipmunk physics engine was developed by Scott Lembcke of Howling Moon Software. Chipmunk was actually inspired by an early version of Box2D, before it was a full-fledged physics engine. You can download Chipmunk from its Google Code site. If you like Chipmunk, you should also consider donating to the project, which you can do via the Donate button from the same site: http://code.google.com/p/chipmunk-physics. The Chipmunk documentation is located on Scott's home page, at http://files.slembcke.net/chipmunk/release/ChipmunkLatest-Docs. And if you need help, you can find that in the Chipmunk forums: www.slembcke.net/forums.

Objectified Chipmunk

There are actually two Objective-C wrappers available for Chipmunk, and more are being worked on. I don't discuss them in this book, but you should be aware of them and try them.

Scott's Objective-C wrapper is part of the Chipmunk distribution but works only in the iPhone Simulator. To be able to deploy it to the iPhone, you have to buy his Chipmunk Objective-C wrapper on the Howling Moon Software web site, at http://howlingmoonsoftware.com/objectiveChipmunk.php.

It's always best to try before you buy, so you can follow this tutorial on how to use the Objective-C wrapper for the iPhone Simulator: http://files.slembcke.net/chipmunk/tutorials/SimpleObjectiveChipmunk.

The alternative is Chipmunk SpaceManager, written by Robert Blackwood, which comes with a free Objective-C wrapper for Chipmunk. However, its main purpose is to make integration of Chipmunk with cocos2d easier. If you would like to try Chipmunk SpaceManager, you can download it from the following link, where you can also donate to the project should you like it: http://code.google.com/p/chipmunk-spacemanager.

The SpaceManager API reference can be found on Robert's home page at www.mobile-bros.com/spacemanager/docs.

Both the Chipmunk and SpaceManager distributions, including their respective documentation files, are also in this chapter's source code folder, in the Physics Engine Librariessubfolder.

Chipmunks in Space

For the Chipmunk tutorial, I'll be building the same project as before. I'll start with the PhysicsChipmunk01 project and the initial setup of the Chipmunk physics engine. Listing 12–8 shows the HelloWorldScene header file.

Listing 12–8. The Chipmunk HelloWorld Interface

#import "cocos2d.h"
#import "chipmunk.h"

enum
{
    kTagBatchNode = 1,
};

@interface HelloWorld : CCLayer
{
    cpSpace* space;
}

+(id) scene;

@end

There's nothing unusual here, except for the cpSpace member variable. Instead of world, Chipmunk calls its world a space. It's a different term for the same thing. The Chipmunk space contains all the rigid bodies.

Chipmunk is initialized in HelloWorldScene's init method as follows:

cpInitChipmunk();

space = cpSpaceNew();
space->iterations = 8;
space->gravity = CGPointMake(0, -100);

The very first thing you must do before using any Chipmunk methods is to call cpInitChipmunk. After that, you can create the space with cpSpaceNew and set the number of iterations—in this case eight. This is the same iteration count we used for velocity iterations in the Box2D example's update method. Chipmunk knows only one type of iteration—the elasticIterations field is deprecated and should no longer be used. I mention this in case you are already familiar with Chipmunk. You may get away with fewer than eight iterations if your game does not allow objects to stack; otherwise, you may find that stacked objects never get to rest and keep jittering and sliding for a long period of time.

Notice how Chipmunk can use the same CGPoint structure used in the iPhone SDK. Chipmunk internally uses a structure called cpVect, but in cocos2d you can use both interchangeably. I use a CGPoint to set the gravity to –100, which means downward acceleration that is roughly the same as that used in the Box2D project.

Of course, the space also needs to be released in the dealloc method; this is done by calling cpSpaceFree and passing the space as a parameter:

-(void) dealloc
{
    cpSpaceFree(space);
    [super dealloc];
}

Boxing-In the Boxes

To keep all the boxes within the boundaries of the screen, you need to create a static body whose shape defines the screen area. First, the variables for the screen's corners are defined:

// For the ground body we'll need these values
CGSize screenSize = [CCDirector sharedDirector].winSize;
CGPoint lowerLeftCorner = CGPointMake(0, 0);
CGPoint lowerRightCorner = CGPointMake(screenSize.width, 0);
CGPoint upperLeftCorner = CGPointMake(0, screenSize.height);
CGPoint upperRightCorner = CGPointMake(screenSize.width, screenSize.height);

Contrary to Box2D, you do not have to take any pixel-to-meter ratio into account. You can use the screen size in pixels as it is to define the corner points and to work with Chipmunk bodies in general.

Next you'll create the static body by using the cpBodyNew and passing INFINITY for both parameters, which makes the body a static body. Those parameters are mass and inertia, and with them being set to the INFINITY value, this body isn't going to go anywhere.

// Create the static body that keeps objects within the screen area
float mass = INFINITY;
float inertia = INFINITY;
cpBody* staticBody = cpBodyNew(mass, inertia);

NOTE: Mass and inertia in Chipmunk are comparable to density and friction in Box2D. The difference between inertia and friction is that the former determines the resistance of a body to start moving, while the latter determines how much motion a body loses when it is in contact with other bodies.

Next, you'll define the shape that goes with the body and makes up the screen borders, as shown in Listing 12–9.

Listing 12–9. Creating the Screen Border Collisions

cpShape* shape;
float elasticity = 1.0f;
float friction = 1.0f;
float radius = 0.0f;

// Bottom
shape = cpSegmentShapeNew(staticBody, lowerLeftCorner, lowerRightCorner, radius);
shape->e = elasticity;
shape->u = friction;
cpSpaceAddStaticShape(space, shape);

// Top
shape = cpSegmentShapeNew(staticBody, upperLeftCorner, upperRightCorner, radius);
shape->e = elasticity;
shape->u = friction;
cpSpaceAddStaticShape(space, shape);

// Left side
shape = cpSegmentShapeNew(staticBody, lowerLeftCorner, upperLeftCorner, radius);
shape->e = elasticity;
shape->u = friction;
cpSpaceAddStaticShape(space, shape);

// Right side
shape = cpSegmentShapeNew(staticBody, lowerRightCorner, upperRightCorner, radius);
shape->e = elasticity;
shape->u = friction;
cpSpaceAddStaticShape(space, shape);

The cpSegmentShapeNew method is used to create four new line segments to define the sides of the screen area. The shape variable is reused for convenience, but it requires you to set elasticity (which is the same as restitution) and friction after each call to cpSegmentShapeNew. Then each shape is added to the space as a static shape via the cpSpaceAddStaticShape method.

NOTE: In Chipmunk you will have to work with one-letter fields like e and u regularly. Personally, I find that this makes it hard to pick up Chipmunk because you don't immediately grasp the meaning of these fields, and you have to refer to the Chipmunk documentation more often than necessary.

Adding Ticky-Tacky Little Boxes

To add boxes to the world, I used the same code in the init method of HelloWorldScene as in the Box2D example. Refer to Listing 12–3 if you'd like to refresh your memory.

I'll go straight to creating the dynamic body for new boxes, which is what the addNewSpriteAt method does (Listing 12–10).

Listing 12–10. Adding a Body with a Sprite, Chipmunk Style

-(void) addNewSpriteAt:(CGPoint)pos
{
    float mass = 0.5f;
    float moment = cpMomentForBox(mass, TILESIZE, TILESIZE);
    cpBody* body = cpBodyNew(mass, moment);

    body->p = pos;
    cpSpaceAddBody(space, body);

    float halfTileSize = TILESIZE * 0.5f;
    int numVertices = 4;
    CGPoint vertices[] =
    {
        CGPointMake(-halfTileSize, -halfTileSize),
        CGPointMake(-halfTileSize, halfTileSize),
        CGPointMake(halfTileSize, halfTileSize),
        CGPointMake(halfTileSize, -halfTileSize),
    };

    CGPoint offset = CGPointZero;
    float elasticity = 0.3f;
    float friction = 0.7f;

    cpShape* shape = cpPolyShapeNew(body, numVertices, vertices, offset);
    shape->e = elasticity;
    shape->u = friction;
    shape->data = [self addRandomSpriteAt:pos];
    cpSpaceAddShape(space, shape);
}

The dynamic body for the box is created with the cpBodyNew method with the given mass and a moment of inertia. The moment of inertia determines the resistance of a body to move, and it's calculated by the helper method cpMomentForBox, which takes the body's mass and the size of the box—in this case TILESIZE—which makes it a 32×32-pixel box.

The body's position, p, is then updated, and the body is added to the space via the cpSpaceAddBody method. Note that contrary to Box2D, you do not have to convert pixels to meters; you can work with pixel coordinates directly.

Then a list of vertices are created, which will become the corners of the box shape. Because the corner positions are positioned relative to the center of the box we're creating, they are all half a tile's size away from the center. Otherwise, the box shape would be twice as big as the tile.

The cpPolyShapeNew method then takes the body as input, the vertices array, and the number of vertices in the array, as well as an optional offset, which is set to CGPointZero in this case. Out comes a new cpShape pointer for the box shape. The shape's elasticity and friction are set to values that give a similar behavior to the Box2D boxes, and after the sprite is set as user data to the data field, the shape is added to the space via cpSpaceAddShape.

The addRandomSpriteAt method in Listing 12–11 simply creates the CCSprite object that goes along with the new dynamic body.

Listing 12–11. Creating New Box Objects with Random Images

-(CCSprite*) addRandomSpriteAt:(CGPoint)pos
{
    CCSpriteBatchNode* batch = (CCSpriteBatchNode*)[self getChildByTag:kTagBatchNode];

    int idx = CCRANDOM_0_1() * TILESET_COLUMNS;
    int idy = CCRANDOM_0_1() * TILESET_ROWS;
    CGRect tileRect = CGRectMake(TILESIZE * idx, TILESIZE * idy, TILESIZE, TILESIZE);
    CCSprite* sprite = [CCSprite spriteWithBatchNode:batch rect:tileRect];
    sprite.position = pos;
    [batch addChild:sprite];

    return sprite;
}

Updating the Boxes' Sprites

Just like with Box2D, you have to update the sprite's position and rotation to be in line with their dynamic body's position and rotation every frame. Again, this is done in the update method:

-(void) update:(ccTime)delta
{
    float timeStep = 0.03f;
    cpSpaceStep(space, timeStep);

    // Call forEachShape C method to update sprite positions
    cpSpaceHashEach(space->activeShapes, &forEachShape, nil);
    cpSpaceHashEach(space->staticShapes, &forEachShape, nil);
}

Just as with Box2D, you have to advance the physics simulation using a step method. In this case, it's cpSpaceStep, which takes the space and a timeStep as input. A fixed time step works best, and just like in Box2D, it's highly recommended you use a fixed time step as opposed to passing the delta time. As long as the framerate doesn't fluctuate heavily (it really shouldn't anyway), using a fixed-time-step approach works very well.

The cpSpaceHashEach method calls the C method forEachShape for, well, each of the shapes. Or, more accurately, the cpSpaceHashEach method calls forEachShape for each active shape (dynamic body) and then for each static shape (static body). With the third parameter, you can pass an arbitrary pointer as a parameter to the forEachShape method, but because it's not needed in this case, it is set to nil. And even though this example project doesn't have static shapes with sprites assigned to them, it nevertheless calls the method for static shapes, just in case you want to be adding some static shapes with a sprite.

The forEachShape method is a callback method that's written in C. In the example project, you can find it at the top of the HelloWorldScene.mfile, outside the @implementation. Although it's not strictly necessary to place the method outside the @implementation, it signals that this method isn't part of the HelloWorldScene class. The method is defined as static, which effectively makes it a global method, as Listing 12–12 shows.

Listing 12–12. Updating a Body Sprite's Position and Rotation

// C method that updates sprite position and rotation:
static void forEachShape(void* shapePointer, void* data)
{
    cpShape* shape = (cpShape*)shapePointer;
    CCSprite* sprite = (CCSprite*)shape->data;
    if (sprite != nil)
    {
        cpBody* body = shape->body;
        sprite.position = body->p;
        sprite.rotation = CC_RADIANS_TO_DEGREES(body->a) * -1;
    }
}

The signature for methods passed to cpSpaceHashEach is strictly defined; the method must take two parameters, and both are void pointers. The second parameter would be the data pointer passed as third parameter to cpSpaceHashEach. For both shapePointer and data pointer, you have to know what kind of object they're pointing to; otherwise, disaster will strike in the form of EXC_BAD_ACCESS.

In this case, I know that shapePointer is going to point to a cpShapestruct, so I can safely cast it and then access the shape's data field to get its CCSprite pointer. If the sprite is a valid pointer, I can get the body from the shape and use that to set the position and rotation of the sprite to that of the body. As with Box2D before, the rotation has to be converted from radians to degrees first and then multiplied by –1 to correct the direction of the rotation.

A Chipmunk Collision Course

Collisions in Chipmunk are also handled by C callback methods. In the PhysicsChipmunk02 project, I've added the contactBegin and contactEnd static methods (in Listing 12–13), which do exactly the same as their Box2D counterparts: change the color of boxes that are in contact to magenta.

Listing 12–13. Collision Callbacks a la Chipmunk

static int contactBegin(cpArbiter* arbiter, struct cpSpace* space, void* data)
{
    bool processCollision = YES;

    cpShape* shapeA;
    cpShape* shapeB;
    cpArbiterGetShapes(arbiter, &shapeA, &shapeB);

    CCSprite* spriteA = (CCSprite*)shapeA->data;
    CCSprite* spriteB = (CCSprite*)shapeB->data;
    if (spriteA != nil && spriteB != nil)
    {
        spriteA.color = ccMAGENTA;
        spriteB.color = ccMAGENTA;
    }

    return processCollision;


}

static void contactEnd(cpArbiter* arbiter, cpSpace* space, void* data)
{
    cpShape* shapeA;
    cpShape* shapeB;
    cpArbiterGetShapes(arbiter, &shapeA, &shapeB);

    CCSprite* spriteA = (CCSprite*)shapeA->data;
    CCSprite* spriteB = (CCSprite*)shapeB->data;
    if (spriteA != nil && spriteB != nil)
    {
        spriteA.color = ccWHITE;
        spriteB.color = ccWHITE;
    }
}

The contactBegin method should return YES if the collision should be processed normally. By returning NO or 0 from this method, you can also ignore collisions. To get to the sprites, you first have to get the shapes from the cpArbiter, which just like b2Contact holds the contact information. Via the cpArbiterGetShapes method and passing two shapes as out parameters, you get the colliding shapes from which you can then retrieve the individual CCSprite pointers. If they are both valid, their color can be changed.

As with Box2D, these callbacks don't get called by themselves. In the HelloWorldSceneinit method, right after the space is created, you must add the collision handlers using the cpSpaceAddCollisionHandler method:

unsigned int defaultCollisionType = 0;
cpSpaceAddCollisionHandler(space, defaultCollisionType, defaultCollisionType,images
    &contactBegin, NULL, NULL, &contactEnd, NULL);

The default collision type for shapes is 0, and because I don't care about filtering collisions, both collision type parameters are set to 0. You can assign each body's shape an integer value to its collision_type property and then add collision handlers that are called only if bodies of matching collision types collide. This is called filtering collisions and is described in the Chipmunk manual, at http://files.slembcke.net/chipmunk/release/ChipmunkLatest-Docs/#cpShape.

The next four parameters are pointers to C callback methods for the four collision stages: begin, pre-solve, post-solve, and separation (the same as the EndContact event in Box2D). These serve the same purpose as the corresponding callbacks in Box2D. Most of the time you'll be interested only in the begin and separation events.

I pass NULL for pre-solve and post-solve, because I'm not interested in handling these. You can use these methods to influence the collision or to retrieve the collision force in the post-solve step. The final parameter is an arbitrary data pointer you can pass on to the callback methods if you need it. I don't, so I set it to NULL as well.

With that, you have a working collision callback mechanism.

Joints for Chipmunks

The Chipmunk example project also needs its own implementation of addSomeJoinedBodies. The setup is more verbose than for Box2D, as shown in Listing 12–14. You'll recognize most of the code as setting up static and dynamic bodies—if you find that code familiar, feel free to skip to the end where the joints are created.

Listing 12–14. Creating Three Bodies Connected with Joints

-(void) addSomeJoinedBodies:(CGPoint)pos
{
    float mass = 1.0f;
    float moment = cpMomentForBox(mass, TILESIZE, TILESIZE);

    float halfTileSize = TILESIZE * 0.5f;
    int numVertices = 4;
    CGPoint vertices[] =
    {
        CGPointMake(-halfTileSize, -halfTileSize),
        CGPointMake(-halfTileSize, halfTileSize),
        CGPointMake(halfTileSize, halfTileSize),
        CGPointMake(halfTileSize, -halfTileSize),
    };

    // Create a static body
    cpBody* staticBody = cpBodyNew(INFINITY, INFINITY);
    staticBody->p = pos;

    CGPoint offset = CGPointZero;
    cpShape* shape = cpPolyShapeNew(staticBody, numVertices, vertices, offset);
    cpSpaceAddStaticShape(space, shape);

    // Create three new dynamic bodies
    float posOffset = 1.4f;
    pos.x += TILESIZE * posOffset;
    cpBody* bodyA = cpBodyNew(mass, moment);
    bodyA->p = pos;
    cpSpaceAddBody(space, bodyA);

    shape = cpPolyShapeNew(bodyA, numVertices, vertices, offset);
    shape->data = [self addRandomSpriteAt:pos];
    cpSpaceAddShape(space, shape);

    pos.x += TILESIZE * posOffset;
    cpBody* bodyB = cpBodyNew(mass, moment);
    bodyB->p = pos;
    cpSpaceAddBody(space, bodyB);

    shape = cpPolyShapeNew(bodyB, numVertices, vertices, offset);
    shape->data = [self addRandomSpriteAt:pos];
    cpSpaceAddShape(space, shape);

    pos.x += TILESIZE * posOffset;
    cpBody* bodyC = cpBodyNew(mass, moment);
    bodyC->p = pos;
    cpSpaceAddBody(space, bodyC);


    shape = cpPolyShapeNew(bodyC, numVertices, vertices, offset);
    shape->data = [self addRandomSpriteAt:pos];
    cpSpaceAddShape(space, shape);

    // Create the joints and add the constraints to the space
    cpConstraint* constraint1 = cpPivotJointNew(staticBody, bodyA, staticBody->p);
    cpConstraint* constraint2 = cpPivotJointNew(bodyA, bodyB, bodyA->p);
    cpConstraint* constraint3 = cpPivotJointNew(bodyB, bodyC, bodyB->p);

    cpSpaceAddConstraint(space, constraint1);
    cpSpaceAddConstraint(space, constraint2);
    cpSpaceAddConstraint(space, constraint3);
}

In this example, I'm creating a pivot joint with cpPivotJointNew, which is the same as the b2RevoluteJoint used in the Box2D example. Each joint is created with the two bodies that should be connected to each other and one of the bodies' center position as the anchor point. The cpPivotJointNew method returns a cpConstraint pointer, which you'll have to add to the space using the cpSpaceAddConstraint method.

Summary

In this chapter, you learned the basics of the two physics engines distributed with cocos2d: Box2D and Chipmunk. You now have two working examples of these physics engines at your disposal, which should help you decide which one you'd like to use.

You learned how to set up a screen area that contains all the little boxes created from a tilemap as dynamic bodies. You now also know the basics of detecting collisions and how to create joints to connect bodies together in both physics engines.

In the next chapter, you'll be making a game that uses the Box2D physics engine.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.30.178