Box2DWeb in Cocos2D-HTML5 games

Introduction

The aim of this article is to familiarize the developer with Box2dWeb and describe how to use it in Tizen applications. These topics are illustrated with a sample application. It is a continuation of a series of articles on Cocos2D-HTML5 game (How to use Tiled Map Editor with Cocos2d-html5).

A sample game called ""Run Snail Run" is provided to demonstrate the use of physics and graphics libraries. It is a new version of the application used in previous articles with Box2dWeb based collision detection added.

NOTE: The sample application, attached to this article, requires a device supporting the DEVICE ORIENTATION  event.

Box2dWeb

Box2d is a physics engine for 2d games. It's an open source library distributed under the zlib license. It has been ported to many different programming languages and environments including Java, Adobe Flash, C# and JavaScript.
In this sample application we use one of the two major Box2d ports to JavaScript: Box2dWeb (http://code.google.com/p/box2dweb/). It's more up to date than the other major port - Box2dJS (http://box2d-js.sourceforge.net/). It can also be stored in a single file, which is another advantage.
There's no dedicated documentation for Box2dWeb, but you can use Box2dFlash documentation: http://www.box2dflash.org/docs/2.1a/reference/. Box2dWeb is compiled from Box2dFlash and both JavaScript 1.6 and ActionScript 2.0 are based on the same ECMAScript standard, so the structure of both libraries is organized similarly. There's also a very useful and comprehensive tutorial on Box2d at http://www.box2d.org/manual.html.

Box2dWeb is a physics engine. All the graphics in our sample application are created using canvas. The library simulates rigid body movement and interactions. Bodies can be composed of polygons, circles and edge shapes. You can apply to them forces like gravity, friction and restitution.

Adding Box2dWeb to a Tizen application

Including Box2dWeb to a Tizen application is very easy. All you have to do is to import the library file into your project and declare it in the index.html file:

<script type="text/javascript" src="./js/lib/external/Box2dWeb-2.1.a.3.min.js">

You can now access Box2dWeb functions from your JavaScript files.

Creating a Box2dWeb world

The first step is to create a Box2dWeb world object. It manages the memory, objects and simulations. In our sample application we create and manage the Box2dWeb world in a separate game module.

var world = new b2World(new b2Vec2(0, 0) // gravity
, true // allow sleep
);

The constructor takes two parameters: gravity and allowSleep. In RunSnailRun game we use Box2dWeb only for collision detection. Body movements are controlled by game logics, so we set the gravity vector to zero. The second parameter allowSleep determines whether to allow the bodies to go into the sleeping state when they come to rest. It prevents too much CPU overhead.

Creating bodies

Box2dWeb bodies are the basic objects used by the library. You can define their position, shape and other parameters like friction or density.

To create a body you have to follow these three steps:
1. Create the body definition
For snails and the hedgehog we use dynamic bodies:

var bodyDef = new Box2D.Dynamics.b2BodyDef;
bodyDef.type = Box2D.Dynamics.b2Body.b2_dynamicBody;

2. Define the fixture
Fixtures attach shapes to bodies and add material properties such as density, friction and restitution. One fixture holds a single shape. A body can have many fixtures.

Creating a fixture for hedgehog is shown in the following code sample. The hedgehog is round so we can use a circle shape.

//create fixture object
var fixDef = new Box2D.Dynamics.b2FixtureDef;

//define basic parameters
fixDef.density = 1.0;
fixDef.friction = 0.5;
fixDef.restitution = 0.2;
fixDef.isSensor = true;

// canvas width and height in meters
var height = document.documentElement.clientHeight / game.config.box2dScale;
var width = document.documentElement.clientWidth / game.config.box2dScale;

//define shape
fixDef.shape = new Box2D.Collision.Shapes.b2CircleShape(height / 45);

Shape's dimensions are relative to canvas width and height. Notice the isSensor parameter in the fixture definition. True value means that even though the bodies won't collide and change direction we still will be notified when they overlap.

3. Bind the fixture to the body and add it to the world
Finally you have to set the body position, bind the fixture to the body definition and add the body to the world:

bodyDef.position.x = width / 2;
bodyDef.position.y = -height / 2;
this.hedgehogBody = game.getBox2dWorld().CreateBody(bodyDef);
this.hedgehogBody.CreateFixture(fixDef);

Creating custom shapes

Sometimes default rectangle and circle shapes can't precisely define game object. Hedgehog object was nearly perfectly round, but snail's shape can't be created that easily. In Box2dWeb you can define your own polygon shapes. They can only be of the convex polygons. It means that:
- every internal angle is less than or equal to 180 degrees,
- every line segment between two vertices remains inside or on the boundary of the polygon.
To create a polygon shape you have to provide its coordinates. To find the vertex table you can use a tool. There is a popular application VertexHelper Pro (http://itunes.apple.com/us/app/vertexhelper-pro/id411684411?mt=12), but it's not free and is available only for Mac. Instead you can use one of the community created tools like Andengine Vertex Helper (http://www.andengine.org/forums/features/vertex-helper-t1370.html). To make creating vertex table easier you can change the default pattern vector to for example: %+.5f, %+.5f .

Andengine vertex helperAndengine Vertex Helper

Once you have found the right coordinates you can create a vector of b2Vec2 objects. You can translate coordinates' values to Box2dWorld with a ptmRatio (pixel-to-meters ratio). Then a b2Polygon shape can be created as shown in the below code sample.

var ptmRatio = height / 22;// scale snail shape to graphics size on the screen

var v = [ [ -0.537508 * ptmRatio, -0.40000 * ptmRatio ], [ 0.35833 * ptmRatio, -0.36250 * ptmRatio ], [ 0.52500 * ptmRatio, 0.37083 * ptmRatio ],
        [ -0.42917 * ptmRatio, 0.37500 * ptmRatio ] ];// vector defining shape of the snail, coordinates determined using Andengine Vertex Helper tool

var vecs = [];
for ( var j = 0; j < v.length; j++) {
    var tmp = new Box2D.Common.Math.b2Vec2();
    tmp.Set(v[j][0], v[j][1]);
    vecs[j] = tmp;
}

fixDef.shape = new Box2D.Collision.Shapes.b2PolygonShape;
fixDef.shape.SetAsArray(vecs, vecs.length);

Storing additional data

Box2dWeb bodies can store some additional user data. In the sample application we use it to keep the information about the object type (snail or hedgehog).

this.hedgehogBody = game.getBox2dWorld().CreateBody(bodyDef);
this.hedgehogBody.SetUserData("hedgehog");

To get the user data you can use GetUserData() function.

this.hedgehogBody.GetUserData(); //returns "hedgehog"

Setting debug draw

Box2dWeb doesn't provide any functions for displaying bodies. It is responsible for calculating positions of bodies and interactions. In RunSnailRun game positions of objects are calculated by game logic and Box2dWeb is used only for collision detection.

Debug modeDebug mode

It has also a debug mode in which you can see transparent Box2dWeb bodies. This is very useful for debugging purposes, because you can check whether all the objects look and act the way they should. In this sample application the debug mode can be switched on/off with config.box2dDebug value in game.js file. We recommend to use the debug mode on simulator, because rendering Box2dWeb debug data works faster there.

RunSnailRun uses WebGL to render the graphics. To show the debug data we use a separate canvas, covering the whole game board. For Box2dWeb it has to be used in 2d context.

<canvas id="box2d" width="1260" height="660">

Box2dWeb debug mode doesn't use Cartesian coordinates system. The x axis values increase rightward, while the y axis values increase downwards. So we have to translate Cocos2d Cartesian coordinates to Box2dWeb world. It is explained in more detail in "Simulating the world" part of this article.

All operations for setting debug mode parameters are in the game module. They can be accessed from level modules with setBox2dDebug() function.
setBox2dDebug : function() {
    if (this.config.box2dDebug) {
        var b2DebugDraw = Box2D.Dynamics.b2DebugDraw;
        var debugDraw = new b2DebugDraw();
        debugDraw.SetSprite(document.getElementById("box2d").getContext("2d"));
        debugDraw.SetDrawScale(this.config.box2dScale);
        debugDraw.SetFillAlpha(0.8);
        debugDraw.SetLineThickness(1.0);
        debugDraw.SetFlags(b2DebugDraw.e_shapeBit);
        world.SetDebugDraw(debugDraw);
    }
}

Notice the scale value in the sample code above. It is set in config.box2dScale (here it's equal 30). Box2dWeb uses its own unit system. When rendering the graphic you need to convert it to pixel coordinates. The scale value of 30 means that 1 meter = 30 pixels.
Debug information is drawn on the canvas in update function with world.DrawDebugData() function (see "Simulating the world" part of this article).

Collision detection

Accurate collision detection was the main purpose of using Box2dWeb in this sample application. The basic version of RunSnailRun used a quicker solution of intersecting rectangles (refer to "Cocos2D-HTML5 game framework in Tizen applications: Follow-up"). Using b2PolygonShape enables developer to define precise shape bounds.
Box2dWeb provides listener for contact detection: Dynamics.b2ContactListener. It has four functions:
- begin contact event,
- end contact event,
- pre-solve event,
- post-solve event.
To set this contact listener SetContactListener function is used.

// Add listeners for contact
var listener = new Box2D.Dynamics.b2ContactListener;
var that = this;

listener.BeginContact = function(contact) {

    if ((contact.GetFixtureA().GetBody().GetUserData() == 'hedgehog' && contact.GetFixtureB().GetBody().GetUserData() == 'snail')
            || (contact.GetFixtureA().GetBody().GetUserData() == 'snail' && contact.GetFixtureB().GetBody().GetUserData() == 'hedgehog')) {

        for ( var i = 0; i < that.snailsBodies.length; i++) {
            // check if it is a snail-hedgehog collision (if yes, remove the snail)
            if (that.snailsBodies[i] === contact.GetFixtureA().GetBody() || that.snailsBodies[i] === contact.GetFixtureB().GetBody()) {
                that.snailToRemove = i; // mark snail to remove
            }
        }
    }
}

// set contact listener to the world
game.getBox2dWorld().SetContactListener(listener);

In sample application only begin contact event is used. It gets a b2Contact object as an event handler function parameter. This object contains two colliding fixtures: A and B. The first thing we have to do is to determine if it's a hedgehog-snail collision (not snail-snail). Then we find the snail involved in collision. We can't remove Box2d bodies from inside the listener because the bodies passed to it are locked. So we store the index of the snail we want to remove in this.snailForRemoval variable. It is periodically checked in update function.

update : function(dt) {
    ...
    if (this.snailToRemove != null) {
        this.removeSnail(this.snailToRemove);
        this.snailToRemove = null;
    }
},

If its value is not null then the snail is removed with removeSnail function.

removeSnail : function(index) {
    this.removeChild(this.snails[index]);
    this.numberOfSnails--;
    this.snails.splice(index, 1);
    game.getBox2dWorld().DestroyBody(this.snailsBodies[index]);
    this.removeChild(this.snailsBodies[index]);
    this.snailsBodies.splice(index, 1);
    if (this.numberOfSnails === 0) {
         ...
        // end game level
    }
},

Removing the snails works exactly the same way as in the first version of application, without Box2d. We also have to destroy the snail's body.

Simulating the world

For proper collision detection we need to update bodies positions in regular intervals of time. Usually applications use Box2dWeb physics world to determine how the object should move (refer to Custom 3D graphics on Tizen article). In RunSnailRun it is done by game logic. We set calculated positions to Box2dWeb bodies using setPosition function. It takes a b2Vec2 objects as arguments.

As mentioned in the "Setting debug draw" part of this article, Box2dWeb uses a different coordinate system than Cocos2d, so we have to translate the coordinates. All pixel values are divided by Box2dWeb scale value, game.config.box2dScale. The output values are in meters. Additionally we have to change the vertical position values. In Box2dWeb y axis increases downwards, while in Cocox2d it increases upwards. The calculations are shown in the code snippet below.

// set new box2dweb bodies positions
this.hedgehogBody.SetPosition(new Box2D.Common.Math.b2Vec2(this.hedgehog.getX() / game.config.box2dScale, game.config.heightInMeters
        - (this.hedgehog.getY() / game.config.box2dScale)));

for ( var i = 0; i < this.snails.length; i++) {
    this.snailsBodies[i].SetPosition(new Box2D.Common.Math.b2Vec2(this.snails[i].getX() / game.config.box2dScale, game.config.heightInMeters
            - (this.snails[i].getY() / game.config.box2dScale)));
}

Box2dWeb refreshes world parameters with a constant frequency. You can set it in world.Step function.

Function syntax: world.Step(timeStep, velocityIterations, positionIterations);

It takes two additional parameters: velocity and number of position iterations.

world.Step(1 / 60, 10, 10);

The above values come from the demo distributed with the library. You can choose your own parameters. The higher these values, the more accurate the calculations done by Box2dWeb (at the expense of performance). To learn more refer to Box2d manual or Custom 3D graphics on Tizen article. In RunSnailRun game these functions are not important as we don't use Box2dWeb for body movement simulation.

If we are in the debug mode we draw debug data in each iteration of world simulation:

world.DrawDebugData();

Both world.Step and world.DrawDebugData functions are placed in a single function in game module: updateBox2dWeb. It is called in regular intervals of time from the Level module:

window.setInterval(this.updateBox2d, 1000 / 120, game.getBox2dWorld());

Cleaning

After each level finish you have to clean the Box2dWeb world. You can do it with a single DestroyBody function on hedgehog body and on each of the remaining snail bodies.

cleanGameObjects : function() {
    for ( var i = 0; i < this.snails.length; i++) {
         ...
        game.getBox2dWorld().DestroyBody(this.snailsBodies[i]);
        this.removeChild(this.snailsBodies[i]);
         ...
    }
    game.getBox2dWorld().DestroyBody(this.hedgehogBody);
},

Summary

It's the last part of series of articles on game development with Cocos2d-html5. This article shows how to use Box2dWeb in Tizen applications. It explains Box2dWeb basics and focuses on using it for collision detection. Sample application, RunSnailRun game, demonstrates the discussed topics.

File attachments: