Box2D JaveScript Tutorial

07 Feb 2012

This is tutorial is written as a compliment to my @Sthlmjs presentation at 7 February 2012. The presentation was called “Box2D-web - Uncovering The Magic Behind SilarApp” and refers to the SilarApp project that I work in together with @himynameisjonas and @ingmr.

The tutorial explains how to the demo above was made and you can find the source at Github.com/javve/box2d-javascript. I strongly encourages you to download the source and look at it while reading this tutorial.

I will go through the demo step by step in order of execution.

Background

Box2D is a open source physics engine originally written by Erin Catto in C++. Over the years it has been ported to a number of different languages. Today there are two version in JavaScript, Box2d-web and Box2d-js. The latter one is sadly no longer supported so my suggestion is using the first. It is a direct port from Box2DFlash 2.1a and is the one that this tutorial is using.

Good things to know

Getting started

  1. Download Box2d-web
  2. Include box2d-web-2.1.a.3.js at your page.
  3. Create a <canvas> with including height, width and id.
<canvas id="box2d-demo" width="960" height="480" backgroundColor="2A3038"></canvas>
<script src="box2d-web-2.1.a.3.js"></script>

Basic demo structure

// First: Create some Box2D shortcuts.

// Initiate all local variables used in this demo

// Handles all initiation stuff (that is not directly related to box2d)
var init = { .. };

// Used for adding boxes and circles
var add = { .. };

// Contains all functions that interacts with Box2d (except those in var loop = {..};
var box2d = { .. };

// Contains the functions that are called over and over again to make stuff move
var loop = { .. };

// Various helpers
var helpers = { .. };

// The base Shape and Box & Circle that share the Shape prototype
var Shape = function(){ .. };
var Circle = function(){ .. };
var Box = function(){ .. };

Initiation

The initiation is done by the var init = { .. } object that contains a collection of function that are needed to set up the demo.

It begins with init.start() that executes the other functions.

world = new b2World(
	new b2Vec2(0, 10)		// gravity
	, false					// allow sleep
);

Adding shapes

When an user clicks on the <canvas> the function add.random() is called with calls add.box() or add.circle(). Here is the code for creating a Circle.

options.radius = 0.5 + Math.random()*1;
var shape = new Circle(options);
shapes[shape.id] = shape;
box2d.addToWorld(shape);

As you can see it first creates a Circle object (that is used to paint the object on the screen). Then it calls box2d.addToWorld() that adds the shape to Box2D. Next section explains how that function works.

About Box2D Bodies and Fixtures

All elements in Box2D are called bodies and each body have once or more fixtures. Usually you see it like bodies are containers which have a position and then contains fixtures that have a shape, density, friction and collision control. In this demo all fixtures uses the sam default fixture definition.

Bodies

Fixtures

Here are the code that handles body and fixture-related stuff in this demo.

// Initiated earlier
var fixDef = new b2FixtureDef;
fixDef.density = 1.0;
fixDef.friction = 0.5;
fixDef.restitution = 0.2;

var box2d = {
    addToWorld: function(shape) {
        var bodyDef = this.create.bodyDef(shape);
        var body = this.create.body(bodyDef);
        if (shape.radius) {
            this.create.fixtures.circle(body, shape);
        } else {
            this.create.fixtures.box(body, shape);
        }
    },
    create: {
        world: function() { .. },
        defaultFixture: function() { .. },
        bodyDef: function(shape) {
            var bodyDef = new b2BodyDef;
    
            if (shape.isStatic == true) {
                bodyDef.type = b2Body.b2_staticBody;
            } else {
                bodyDef.type = b2Body.b2_dynamicBody;
            }
            bodyDef.position.x = shape.x;
            bodyDef.position.y = shape.y;
            bodyDef.userData = shape.id;
            bodyDef.angle = shape.angle;
        
            return bodyDef;
        },
        body: function(bodyDef) {
            return world.CreateBody(bodyDef);
        },
        fixtures: {
            circle: function(body, shape) {
                fixDef.shape = new b2CircleShape(shape.radius);
                body.CreateFixture(fixDef);
            },
            box: function(body, shape) {
                fixDef.shape = new b2PolygonShape;
                fixDef.shape.SetAsBox(shape.width / 2, shape.height / 2);
                body.CreateFixture(fixDef);
            }
        }
    },
    get: function() {  }
};

The loop

The loop is the center of each Box2D application (or other game etc). In this demo it has three steps.

 
// On my signal: Unleash hell.
(function hell() {
    loop.step();
    loop.update();
    loop.draw();
    requestAnimFrame(hell);
})();

var loop = {
    step: function() {
        var stepRate = 1 / 60;
        world.Step(stepRate, 10, 10);
        world.ClearForces();
    },
    update: function () {            
        for (var b = world.GetBodyList(); b; b = b.m_next) {
            if (b.IsActive() && typeof b.GetUserData() !== 'undefined' && b.GetUserData() != null) {
                shapes[b.GetUserData()].update(box2d.get.bodySpec(b));
            }
        }
        needToDraw = true;
    },
    draw: function() {
        if (!needToDraw) return;
        if (!debug) ctx.clearRect(0, 0, canvas.width, canvas.height);
        
        for (var i in shapes) {
            shapes[i].draw(ctx);
        }
        needToDraw = false;
    }
};

First one thing: requestAnimFrame() is simply a helper function that makes animation smarter. Read more about it here.

  1. The first step loop.step(); tells the Box2D world-object to take one step, and it is here where all the magic happens (moving, collision detection etc).
  2. The next step loop.update(); collects the updates from Box2D and transfer them to our own shapes (Circle and Box).
  3. The last step loop.draw() redraws the <canvas> with all the updated shapes.

About drawing

Drawing shapes/object on the screen is done with regular canvas-methods etc. Both Circleand Box have their own .draw() method. I will not explain these in detail, but they basically get the position from the world object and then uses it to paint itself on the canvas with help of the ctx. Here it the .draw() method from Box.

this.draw = function() {
    ctx.save();
    ctx.translate(this.x * SCALE, this.y * SCALE);
    ctx.rotate(this.angle);
    ctx.translate(-(this.x) * SCALE, -(this.y) * SCALE);
    ctx.fillStyle = this.color;
    ctx.fillRect(
        (this.x-(this.width / 2)) * SCALE,
        (this.y-(this.height / 2)) * SCALE,
        this.width * SCALE,
        this.height * SCALE
    );
    ctx.restore();
};

Wrapping up

That’s it. I hope you liked the tutorial and if you have any questions or suggestions feel free to ping me at Twitter @javve.

Hopefully will I follow up this tutorial with a new one and with more demos at Github.

Continue reading

About Jonny

A swedish maker and product designer. I enjoy the full process of making web products. Some people would call me a full hybrid.

I spend my days with both design and programming as well as marketing and product strategy. During the last fifteen years I've started project, founded and failed startups, almost earned two master degrees in engineering and met hundreds of inspiring friends.

When I'm not making web products, I like organising hackathons, parties and meetups.

Follow me at Twitter, do it!
...or send me and email at jonny.stromberg@gmail.com