javascript 版的 Box2D
作者: 刘鹏
日期: 2009-03-09
本文介绍了用 javascript 实现的 2d 物理引擎 Box2D,利用它可以在浏览器中实现带丰富物理效果的游戏和 UI。

简介

Box2DJS 是用 Javascript 实现的 Box2D 物理引擎,事实上它的代码由 Box2DFlashAS3 1.4.3.1 转换而来。

Box2DJS 使用 2D 命令往 canvas 标签定义的区域中绘制,Firefox、 Safari 和 Opera 9 都支持 canvas 标签,同时Box2DJS 也实现了一个 ExploreCanvas 标签供不支持 Canvas 标签的 IE 浏览器使用。

Box2DJS 的源码主要包括如下内容:

  • js,这里是物理引擎的核心代码,包括几何形状的绘制、动态效果的实现以 及碰撞检测等;
  • lib,这里实现了 ExploreCanvas 标签,在 IE 浏览器中使用;
  • style,这里保存了源码用到的 CSS 样式表;
  • demos,这里提供了一个 demo。

使用 Box2DJS 遵循下面两个步骤:

  1. 把 js/ 和 lib/ 目录拷到你的应用程序目录中;
  2. 在你的 index.html 源码 head 标签中增加 script 标签。

摆动的小球

这个例子是一个小球悬挂在空中左右摆动,随着摆动时间越来越长,小球摆动振 幅越来越小。截图如下所示:

程序序列图如下所示:

源代码如下所示:

index.html 代码如下所示:

/* index.html */

 <html>
  <head>
    <!--=============================-->
    <!-- Copy this part to your app. -->
    <!-- START                       -->
    <!--=============================-->
    <!-- libs -->
    <!--[if IE]><script type="text/javascript" src="lib/excanvas.js"></script><![endif]-->
    <script src="lib/prototype-1.6.0.2.js"></script>

        <!-- box2djs -->
    <script src='js/box2d/common/b2Settings.js'></script>
    <script src='js/box2d/common/math/b2Vec2.js'></script>
    <script src='js/box2d/common/math/b2Mat22.js'></script>
    <script src='js/box2d/common/math/b2Math.js'></script>
    <script src='js/box2d/collision/b2AABB.js'></script>
    <script src='js/box2d/collision/b2Bound.js'></script>
    <script src='js/box2d/collision/b2BoundValues.js'></script>
    <script src='js/box2d/collision/b2Pair.js'></script>
    <script src='js/box2d/collision/b2PairCallback.js'></script>
    <script src='js/box2d/collision/b2BufferedPair.js'></script>
    <script src='js/box2d/collision/b2PairManager.js'></script>
    <script src='js/box2d/collision/b2BroadPhase.js'></script>
    <script src='js/box2d/collision/b2Collision.js'></script>
    <script src='js/box2d/collision/Features.js'></script>
    <script src='js/box2d/collision/b2ContactID.js'></script>
    <script src='js/box2d/collision/b2ContactPoint.js'></script>
    <script src='js/box2d/collision/b2Distance.js'></script>
    <script src='js/box2d/collision/b2Manifold.js'></script>
    <script src='js/box2d/collision/b2OBB.js'></script>
    <script src='js/box2d/collision/b2Proxy.js'></script>
    <script src='js/box2d/collision/ClipVertex.js'></script>
    <script src='js/box2d/collision/shapes/b2Shape.js'></script>
    <script src='js/box2d/collision/shapes/b2ShapeDef.js'></script>
    <script src='js/box2d/collision/shapes/b2BoxDef.js'></script>
    <script src='js/box2d/collision/shapes/b2CircleDef.js'></script>
    <script src='js/box2d/collision/shapes/b2CircleShape.js'></script>
    <script src='js/box2d/collision/shapes/b2MassData.js'></script>
    <script src='js/box2d/collision/shapes/b2PolyDef.js'></script>
    <script src='js/box2d/collision/shapes/b2PolyShape.js'></script>
    <script src='js/box2d/dynamics/b2Body.js'></script>
    <script src='js/box2d/dynamics/b2BodyDef.js'></script>
    <script src='js/box2d/dynamics/b2CollisionFilter.js'></script>
    <script src='js/box2d/dynamics/b2Island.js'></script>
    <script src='js/box2d/dynamics/b2TimeStep.js'></script>
    <script src='js/box2d/dynamics/contacts/b2ContactNode.js'></script>
    <script src='js/box2d/dynamics/contacts/b2Contact.js'></script>
    <script src='js/box2d/dynamics/contacts/b2ContactConstraint.js'></script>
    <script src='js/box2d/dynamics/contacts/b2ContactConstraintPoint.js'></script>
    <script src='js/box2d/dynamics/contacts/b2ContactRegister.js'></script>
    <script src='js/box2d/dynamics/contacts/b2ContactSolver.js'></script>
    <script src='js/box2d/dynamics/contacts/b2CircleContact.js'></script>
    <script src='js/box2d/dynamics/contacts/b2Conservative.js'></script>
    <script src='js/box2d/dynamics/contacts/b2NullContact.js'></script>
    <script src='js/box2d/dynamics/contacts/b2PolyAndCircleContact.js'></script>
    <script src='js/box2d/dynamics/contacts/b2PolyContact.js'></script>
    <script src='js/box2d/dynamics/b2ContactManager.js'></script>
    <script src='js/box2d/dynamics/b2World.js'></script>
    <script src='js/box2d/dynamics/b2WorldListener.js'></script>
    <script src='js/box2d/dynamics/joints/b2JointNode.js'></script>
    <script src='js/box2d/dynamics/joints/b2Joint.js'></script>
    <script src='js/box2d/dynamics/joints/b2JointDef.js'></script>
    <script src='js/box2d/dynamics/joints/b2DistanceJoint.js'></script>
    <script src='js/box2d/dynamics/joints/b2DistanceJointDef.js'></script>
    <script src='js/box2d/dynamics/joints/b2Jacobian.js'></script>
    <script src='js/box2d/dynamics/joints/b2Jacobian.js'></script>
    <script src='js/box2d/dynamics/joints/b2GearJoint.js'></script>
    <script src='js/box2d/dynamics/joints/b2GearJointDef.js'></script>
    <script src='js/box2d/dynamics/joints/b2MouseJoint.js'></script>
    <script src='js/box2d/dynamics/joints/b2MouseJointDef.js'></script>
    <script src='js/box2d/dynamics/joints/b2PrismaticJoint.js'></script>
    <script src='js/box2d/dynamics/joints/b2PrismaticJointDef.js'></script>
    <script src='js/box2d/dynamics/joints/b2PulleyJoint.js'></script>
    <script src='js/box2d/dynamics/joints/b2PulleyJointDef.js'></script>
    <script src='js/box2d/dynamics/joints/b2RevoluteJoint.js'></script>
    <script src='js/box2d/dynamics/joints/b2RevoluteJointDef.js'></script>
        <!--=============================-->
        <!-- Copy this part to your app. -->
        <!-- END                         -->
        <!--=============================-->

    /* my app */
    <script src='mytest/mytest.js'></script>

    <link href="style/distant-planet.css" rel="stylesheet" type="text/css" />
    <link href="style/box2d.css" rel="stylesheet" type="text/css" />

  <head>
 </html>

javascript 代码如下所示:


/* mytest.js*/

var world;
var ctx;
var canvasWidth;
var canvasHeight;
var canvasTop;
var canvasLeft;

function createGround(world) {
    var groundSd = new b2BoxDef();
    groundSd.extents.Set(1000, 50);
    groundSd.restitution = 0.2;
    var groundBd = new b2BodyDef();
    groundBd.AddShape(groundSd);
    groundBd.position.Set(-500, 340);
    return world.CreateBody(groundBd)
}

function createBox(world, x, y, width, height, fixed) {
    if (typeof(fixed) == 'undefined') fixed = true;
    var boxSd = new b2BoxDef();
    if (!fixed) boxSd.density = 1.0;
    boxSd.extents.Set(width, height);
    var boxBd = new b2BodyDef();
    boxBd.AddShape(boxSd);
    boxBd.position.Set(x,y);
    return world.CreateBody(boxBd)
}

function createCircle (world, x, y) {
    var circleSd = new b2CircleDef();
    circleSd.density = 1.0;
    circleSd.radius = 20;
    circleSd.restitution = 1.0;
    circleSd.friction = 0;
    var circleBd = new b2BodyDef();
    circleBd.AddShape(circleSd);
    circleBd.position.Set(x,y);
    return world.CreateBody(circleBd);

}

function createJoint (world, circleBody, x, y) {
    var jointDef = new b2RevoluteJointDef();
    jointDef.anchorPoint.Set(x, y);
    jointDef.body1 = world.GetGroundBody();
    jointDef.body2 = circleBody;
    world.CreateJoint(jointDef);
}

function createWorld() {
    var worldAABB = new b2AABB();
    worldAABB.minVertex.Set(-1000, -1000);
    worldAABB.maxVertex.Set(1000, 1000);
    var gravity = new b2Vec2(0, 300);
    var doSleep = true;
    var world = new b2World(worldAABB, gravity, doSleep);
    /*bottom box*/
    createGround(world);
    /*left box*/
    createBox(world, 0, 125, 10, 250);
    /*right box*/
    createBox(world, 500, 125, 10, 250);
    var circleBody = createCircle (world, 150, 10);
    createJoint (world, circleBody, 200, 100);
    return world;
}


function step(cnt) {
    var stepping = false;
    var timeStep = 1.0/60;
    var iteration = 1;
    world.Step(timeStep, iteration);
    ctx.clearRect(0, 0, canvasWidth, canvasHeight);
    drawWorld(world, ctx);
    setTimeout('step(' + (cnt || 0) + ')', 10);
}

Event.observe(window, 'load', function() {
    world = createWorld ();

    ctx = $('canvas').getContext('2d');
    var canvasElm = $('canvas');
    canvasWidth = parseInt(canvasElm.width);
    canvasHeight = parseInt(canvasElm.height);
    canvasTop = parseInt(canvasElm.style.top);
    canvasLeft = parseInt(canvasElm.style.left);
    step ();
});


代码下载地址:http://guitestbed.googlecode.com/files/box2d-js.tar.gz

碰撞

这个例子是一个小球自由落体落下,与一个矩形 box 碰撞后落到地面,截图如下所示:

碰撞前自由落体
碰撞前自由落体
碰撞后自由落体
碰撞后自由落体

js代码:


var world;
var ctx;
var canvasWidth;
var canvasHeight;
var canvasTop;
var canvasLeft;

function createGround(world) {
    var groundSd = new b2BoxDef();
    groundSd.extents.Set(1000, 50);
    groundSd.restitution = 0.2;
    var groundBd = new b2BodyDef();
    groundBd.AddShape(groundSd);
    groundBd.position.Set(-500, 340);
    return world.CreateBody(groundBd)
}


function createBox(world, x, y, width, height, fixed) {
    if (typeof(fixed) == 'undefined') fixed = true;
    var boxSd = new b2BoxDef();
    if (!fixed) boxSd.density = 1.0;
    /*if density of body is 0, then the body is static. */
    //boxSd.density = 1.0;
    boxSd.extents.Set(width, height);
    var boxBd = new b2BodyDef();
    boxBd.AddShape(boxSd);
    boxBd.position.Set(x,y);
    return world.CreateBody(boxBd)
}

function createCircle (world, x, y) {
    var circleSd = new b2CircleDef();
    circleSd.density = 1.0;
    circleSd.radius = 10;
    circleSd.restitution = 0.5;
    circleSd.friction = 0.1;
    var circleBd = new b2BodyDef();
    circleBd.AddShape(circleSd);
    circleBd.position.Set(x,y);
    return world.CreateBody(circleBd);
}

function createWorld() {
    var worldAABB = new b2AABB();
    worldAABB.minVertex.Set(-1000, -1000);
    worldAABB.maxVertex.Set(1000, 1000);
    var gravity = new b2Vec2(0, 300);
    var doSleep = true;
    var world = new b2World(worldAABB, gravity, doSleep);
    /*bottom box*/
    createGround(world);
    /*left box*/
    createBox(world, 0, 125, 10, 250);
    /*right box*/
    createBox(world, 500, 125, 10, 250);

    /*The falling ball*/
    var circleBody = createCircle (world, 150, 10);
    /*The static box*/
    var boxBody = createBox (world, 156, 100, 5, 5);


    return world;
}

function step(cnt) {
    var stepping = false;
    var timeStep = 1.0/60;
    var iteration = 1;
    world.Step(timeStep, iteration);
    ctx.clearRect(0, 0, canvasWidth, canvasHeight);
    drawWorld(world, ctx);
    setTimeout('step(' + (cnt || 0) + ')', 10);
}


Event.observe(window, 'load', function() {

    world = createWorld ();

    ctx = $('canvas').getContext('2d');
    var canvasElm = $('canvas');
    canvasWidth = parseInt(canvasElm.width);
    canvasHeight = parseInt(canvasElm.height);
    canvasTop = parseInt(canvasElm.style.top);
    canvasLeft = parseInt(canvasElm.style.left);
    step ();
});

Reference