Box2D最初是有Erin Catto用C++编写的。很多流行的物理仿真类游戏都使用了该引擎,包含《愤怒的小鸟》,该引擎已经被转化为几种不同语言的版本,包括Java,ActionScript,C#和JavaScript。JS版本又被称为Box2dWeb。
http://code.google.com/p/box2dweb
3.1 Box2D基础
Box2D使用一些基本对象来定义和模拟游戏世界,其中最重要的有如下几个:
- world:世界,Box2D的主对象,包含世界中的所有物体,对游戏中的物理现象进行模拟
- body:刚性的物体,可能由一个或多个形状组成,这些形状通过载具添加到物体上。
- shape:二维形状,如圆或多边形这些Box2D中用到的基本形状。
- fixture:载具,用来向物体上添加形状以监测碰撞。载具还包括一些非形状数据,如摩擦系数、碰撞系数和碰撞阀值。
- joint:接合点,用来以不同的方式将两个物体连接在一起。比如,一个旋转接合点使两个物体共享一个点,它们可以自由地绕着该点旋转。
在游戏中使用Box2D时,首先要定义游戏的world对象,然后使用载具添加物体和形状。接着,逐个操作world对象中的body对象,让Box2D确定它们的位置和状态。最后,将所有body对象绘制出来。最复杂的计算(即确定物体的位置和状态)由Box2D world对象完成。
引入Box2D:box2d.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Box2d Test</title>
<script src="Box2dWeb-2.1.a.3.min.js" type="text/javascript" charset="utf-8"></script>
<script src="box2d.js" type="text/javascript" charset="utf-8"></script>
</head>
<body οnlοad="init();">
<canvas id="canvas" width="640" height="480" style="border:1px solid black;">Your browser does not support HTML5 Canvas</canvas>
</body>
</html>
box2d.js
//为了方便,将常用的对象定义为快捷变量
var b2Vec2 = Box2D.Common.Math.b2Vec2;
var b2BodyDef = Box2D.Dynamics.b2BodyDef;
var b2Body = Box2D.Dynamics.b2Body;
var b2FixtureDef = Box2D.Dynamics.b2FixtureDef;
var b2Fixture = Box2D.Dynamics.b2Fixture;
var b2World = Box2D.Dynamics.b2World;
var b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape;
var b2CircleShape = Box2D.Collision.Shapes.b2CircleShape;
var b2DebugDraw = Box2D.Dynamics.b2DebugDraw;
var b2RevoluteJointDef = Box2D.Dynamics.Joints.b2RevoluteJointDef;
定义World变量:重力加速度
//创建b2World对象
var world;
var scale = 30; //在canvas上的30像素表示Box2d世界中的1米
function init() {
// 创建Box2D world对象,该对象将完成大部分物理计算
var gravity = new b2Vec2(0, 9.8); //重力加速度9.8m/s^2
var allowSleep = true; //允许静止的物体进入休眠状态,休眠物体不参与物理仿真计算
world = new b2World(gravity, allowSleep);
}
添加第一物体: 地面 createFloor()
在Box2D中创建任何物体都需经过以下步骤:
- 使用b2BodyDef声明一个body的预定义对象。该对象包含了物体的细节,如物体的位置(x,y)和物体的类型(静态和动态)。静态物体不会受重力加速度和其他物体的碰撞影响。
- 使用b2FixtureDef声明一个fixture的预定义对象。载具用来给物体指定形状。该对象也包含了一些其他信息,如指定形状的密度、摩擦系数和弹性恢复系数。
- 设置载具指定的形状。这里使用Box2D中两种类型的形状:多边形(b2polygonshape)和圆(b2circleshape)。
- 将物体的预定义对象传入createBody()方法中,返回一个body对象。
- 将载具的预定义对象传入createFixture()方法中,为物体指定形状。
绘制世界:调试绘图模式 setupDebugDraw()
使用DrawDebugData()在给定的canvas上绘制出世界
动画:步骤
- 告诉Box2D为很短时间进行一次模拟。使用world.Step()方法完成这一步
- 将所有的物体重新绘制在新的位置。使用world.DrawDebugData()或这些物体自身的绘制函数
- 使用world.ClearForces()方法清除作用在物体上的力。
//为了方便,将常用的对象定义为快捷变量
var b2Vec2 = Box2D.Common.Math.b2Vec2;
var b2BodyDef = Box2D.Dynamics.b2BodyDef;
var b2Body = Box2D.Dynamics.b2Body;
var b2FixtureDef = Box2D.Dynamics.b2FixtureDef;
var b2Fixture = Box2D.Dynamics.b2Fixture;
var b2World = Box2D.Dynamics.b2World;
var b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape;
var b2CircleShape = Box2D.Collision.Shapes.b2CircleShape;
var b2DebugDraw = Box2D.Dynamics.b2DebugDraw;
var b2RevoluteJointDef = Box2D.Dynamics.Joints.b2RevoluteJointDef;
//创建b2World对象
var world;
var scale = 30; //在canvas上的30像素表示Box2d世界中的1米
function init() {
// 创建Box2D world对象,该对象将完成大部分物理计算
var gravity = new b2Vec2(0, 9.8); //重力加速度9.8m/s^2
var allowSleep = true; //允许静止的物体进入休眠状态,休眠物体不参与物理仿真计算
world = new b2World(gravity, allowSleep);
createFloor();
setupDebugDraw();
animate();
}
//创建地面
function createFloor() {
//body的预定义对象,包含创建body刚体需要用到的所有数据
var bodyDef = new b2BodyDef;
bodyDef.type = b2Body.b2_staticBody; //静态 地面不受重力或其他物体碰撞影响
bodyDef.position.x = 640/2/scale; // 位置 x=320px, y=450px
bodyDef.position.y = 450/scale;
// fixture用来向body添加shape以实现碰撞检测
// fixture的预定义对象,用来建立fixture
var fixtureDef = new b2FixtureDef;
fixtureDef.density = 1.0; //形状的密度、摩擦系数、弹性恢复系数等属性
fixtureDef.friction = 0.5;
fixtureDef.restitution = 0.2;
fixtureDef.shape = new b2PolygonShape; //多边形
fixtureDef.shape.SetAsBox(320/scale, 10/scale); //640宽,20高
var body = world.CreateBody(bodyDef);
var fixture = body.CreateFixture(fixtureDef);
}
//设置调试绘图
var context;
function setupDebugDraw() {
context = document.getElementById("canvas").getContext('2d');
var debugDraw = new b2DebugDraw();
//使用canvas绘图环境来绘制调试画面
debugDraw.SetSprite(context);
//设置绘图比例
debugDraw.SetDrawScale(scale);
//填充的透明度为0.3
debugDraw.SetFillAlpha(0.3);
//线条的宽度为1
debugDraw.SetLineThickness(1.0);
//绘制所有的shape和joint
debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
//设置调制绘图模式
world.SetDebugDraw(debugDraw);
}
//设立Box2D动画循环
var timeStep = 1/60;
//按照Box2D手册建议的迭代数,速度是8,位置是3
var velocityIterations = 8;
var positionIterations = 3;
function animate() {
world.Step(timeStep, velocityIterations, positionIterations);
world.ClearForces();
world.DrawDebugData();
setTimeout(animate, timeStep);
}
3.2 更多的Box2D元素
Box2D允许向世界中添加不同种类的元素,包括:
- 简单形状的物体,如矩形、圆、多边形。
- 复杂的由多个形状组成的物体。
- 接合点,如连接多个物体的旋转接合点
- 接触监听器,允许我们处理碰撞事件。
创建矩形物体:createRectangularBody()
创建圆形物体:createCircularBody()
创建多边形:createSimplePolygonBody()
创建多种形状的复杂物体:createComplexBody()
连接物体的接合点:接合点用来将物体与物体之间连接起来。包括滑轮、齿轮、杆、转动关节和焊接点。
createRevoluteJoint()函数
3.3 追踪碰撞与破坏
在调查一个物体是否被损坏前,需要将该物体与“生命值”或“健康值”联系起来。
创建具有自定义属性的物体:createSpecialBody()
接触监听器:b2ContactListener对象,有四个事件
- BeginContact():两个物体开始接触时被调用
- EndContact():两个物体结束接触时被调用
- PostSolve():求解器完成后调用,进行碰撞检测时很有用
- PreSolve():在求解器求解前调用
实现: listenForContact()函数
摧毁物体:
3.4 绘制角色
//为了方便,将常用的对象定义为快捷变量
var b2Vec2 = Box2D.Common.Math.b2Vec2;
var b2BodyDef = Box2D.Dynamics.b2BodyDef;
var b2Body = Box2D.Dynamics.b2Body;
var b2FixtureDef = Box2D.Dynamics.b2FixtureDef;
var b2Fixture = Box2D.Dynamics.b2Fixture;
var b2World = Box2D.Dynamics.b2World;
var b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape;
var b2CircleShape = Box2D.Collision.Shapes.b2CircleShape;
var b2DebugDraw = Box2D.Dynamics.b2DebugDraw;
var b2RevoluteJointDef = Box2D.Dynamics.Joints.b2RevoluteJointDef;
//创建b2World对象
var world;
var scale = 30; //在canvas上的30像素表示Box2d世界中的1米
function init() {
// 创建Box2D world对象,该对象将完成大部分物理计算
var gravity = new b2Vec2(0, 9.8); //重力加速度9.8m/s^2
var allowSleep = true; //允许静止的物体进入休眠状态,休眠物体不参与物理仿真计算
world = new b2World(gravity, allowSleep);
createFloor();
//创建一些具有简单形状的物体
createRectangularBody();
createCircularBody();
createSimplePolygonBody();
createComplexBody();
createRevoluteJoint();
createSpecialBody();
listenForContact();
setupDebugDraw();
animate();
}
//创建地面
function createFloor() {
//body的预定义对象,包含创建body刚体需要用到的所有数据
var bodyDef = new b2BodyDef;
bodyDef.type = b2Body.b2_staticBody; //静态 地面不受重力或其他物体碰撞影响
bodyDef.position.x = 640/2/scale; // 位置 x=320px, y=450px
bodyDef.position.y = 450/scale;
// fixture用来向body添加shape以实现碰撞检测
// fixture的预定义对象,用来建立fixture
var fixtureDef = new b2FixtureDef;
fixtureDef.density = 1.0; //形状的密度、摩擦系数、弹性恢复系数等属性
fixtureDef.friction = 0.5;
fixtureDef.restitution = 0.2;
fixtureDef.shape = new b2PolygonShape; //多边形
fixtureDef.shape.SetAsBox(320/scale, 10/scale); //640宽,20高
var body = world.CreateBody(bodyDef);
var fixture = body.CreateFixture(fixtureDef);
}
//设置调试绘图
var context;
function setupDebugDraw() {
context = document.getElementById("canvas").getContext('2d');
var debugDraw = new b2DebugDraw();
//使用canvas绘图环境来绘制调试画面
debugDraw.SetSprite(context);
//设置绘图比例
debugDraw.SetDrawScale(scale);
//填充的透明度为0.3
debugDraw.SetFillAlpha(0.3);
//线条的宽度为1
debugDraw.SetLineThickness(1.0);
//绘制所有的shape和joint
debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
//设置调制绘图模式
world.SetDebugDraw(debugDraw);
}
//设立Box2D动画循环
var timeStep = 1/60;
//按照Box2D手册建议的迭代数,速度是8,位置是3
var velocityIterations = 8;
var positionIterations = 3;
function animate() {
world.Step(timeStep, velocityIterations, positionIterations);
world.ClearForces();
world.DrawDebugData();
setTimeout(animate, timeStep);
//自定义绘制
if (specialBody) {
drawSpecialBody();
}
//摧毁耗尽生命值的物体
if (specialBody && specialBody.GetUserData().life <= 0){
//world.DestroyBody(specialBody);
//specialBody = undefined;
console.log("destroyed");
}
setTimeout(animate, timeStep);
}
//创建矩形物体
function createRectangularBody(){
var bodyDef = new b2BodyDef;
bodyDef.type = b2Body.b2_dynamicBody;
bodyDef.position.x = 40/scale;
bodyDef.position.y = 100/scale;
var fixtureDef = new b2FixtureDef;
fixtureDef.density = 1.0;
fixtureDef.friction = 0.5;
fixtureDef.restitution = 0.3;
fixtureDef.shape = new b2PolygonShape; //多边形
fixtureDef.shape.SetAsBox(30/scale, 50/scale); //60宽,100高
var body = world.CreateBody(bodyDef);
var fixture = body.CreateFixture(fixtureDef);
}
//创建一个圆形
function createCircularBody(){
var bodyDef = new b2BodyDef;
bodyDef.type = b2Body.b2_dynamicBody;
bodyDef.position.x = 130/scale;
bodyDef.position.y = 100/scale;
var fixtureDef = new b2FixtureDef;
fixtureDef.density = 1.0;
fixtureDef.friction = 0.5;
fixtureDef.restitution = 0.7;
fixtureDef.shape = new b2CircleShape(30/scale); //
var body = world.CreateBody(bodyDef);
var fixture = body.CreateFixture(fixtureDef);
}
//创建多边形物体
function createSimplePolygonBody(){
var bodyDef = new b2BodyDef;
bodyDef.type = b2Body.b2_dynamicBody;
bodyDef.position.x = 230/scale;
bodyDef.position.y = 50/scale;
var fixtureDef = new b2FixtureDef;
fixtureDef.density = 1.0;
fixtureDef.friction = 0.5;
fixtureDef.restitution = 0.2;
fixtureDef.shape = new b2PolygonShape;
//按顺时针方向创建一个b2Vec2顶点数组
var points = [
new b2Vec2(0,0),
new b2Vec2(40/scale,50/scale),
new b2Vec2(50/scale,100/scale),
new b2Vec2(-50/scale,100/scale),
new b2Vec2(-40/scale,50/scale),
];
fixtureDef.shape.SetAsArray(points, points.length);
var body = world.CreateBody(bodyDef);
var fixture = body.CreateFixture(fixtureDef);
}
// 创建多种形状的复杂物体,由两个形状组成的物体
function createComplexBody(){
var bodyDef = new b2BodyDef;
bodyDef.type = b2Body.b2_dynamicBody;
bodyDef.position.x = 350/scale;
bodyDef.position.y = 50/scale;
var body = world.CreateBody(bodyDef);
//创建第一个载具并为物体添加圆形状
var fixtureDef = new b2FixtureDef;
fixtureDef.density = 1.0;
fixtureDef.friction = 0.5;
fixtureDef.restitution = 0.7;
fixtureDef.shape = new b2CircleShape(40/scale); //
body.CreateFixture(fixtureDef);
//创建第二个
fixtureDef.shape = new b2PolygonShape;
//按顺时针方向创建一个b2Vec2顶点数组
var points = [
new b2Vec2(0,0),
new b2Vec2(40/scale,50/scale),
new b2Vec2(50/scale,100/scale),
new b2Vec2(-50/scale,100/scale),
new b2Vec2(-40/scale,50/scale),
];
fixtureDef.shape.SetAsArray(points, points.length);
var fixture = body.CreateFixture(fixtureDef);
}
//创建转动关节
function createRevoluteJoint(){
//定义第一个物体
var bodyDef1 = new b2BodyDef;
bodyDef1.type = b2Body.b2_dynamicBody;
bodyDef1.position.x = 480/scale;
bodyDef1.position.y = 50/scale;
var body1 = world.CreateBody(bodyDef1);
var fixtureDef1 = new b2FixtureDef;
fixtureDef1.density = 1.0;
fixtureDef1.friction = 0.5;
fixtureDef1.restitution = 0.5;
fixtureDef1.shape = new b2PolygonShape; //多边形
fixtureDef1.shape.SetAsBox(50/scale, 10/scale); //60宽,100高
body1.CreateFixture(fixtureDef1);
//定义第二个物体
var bodyDef2 = new b2BodyDef;
bodyDef2.type = b2Body.b2_dynamicBody;
bodyDef2.position.x = 470/scale;
bodyDef2.position.y = 50/scale;
var body2 = world.CreateBody(bodyDef2);
var fixtureDef2 = new b2FixtureDef;
fixtureDef2.density = 1.0;
fixtureDef2.friction = 0.5;
fixtureDef2.restitution = 0.5;
fixtureDef2.shape = new b2PolygonShape; //多边形
var points = [
new b2Vec2(0,0),
new b2Vec2(40/scale,50/scale),
new b2Vec2(50/scale,100/scale),
new b2Vec2(-50/scale,100/scale),
new b2Vec2(-40/scale,50/scale),
];
fixtureDef2.shape.SetAsArray(points, points.length);
body2.CreateFixture(fixtureDef2);
//创建接合点连接body1和body2
var jointDef = new b2RevoluteJointDef;
var jointCenter = new b2Vec2(470/scale, 50/scale);
jointDef.Initialize(body1, body2, jointCenter);
world.CreateJoint(jointDef);
}
//创建具有自定义属性的物体
var specialBody;
function createSpecialBody(){
var bodyDef = new b2BodyDef;
bodyDef.type = b2Body.b2_dynamicBody;
bodyDef.position.x = 450/scale;
bodyDef.position.y = 0/scale;
specialBody = world.CreateBody(bodyDef);
specialBody.SetUserData({name:"special", life:250});
var fixtureDef = new b2FixtureDef;
fixtureDef.density = 1.0;
fixtureDef.friction = 0.5;
fixtureDef.restitution = 0.5;
fixtureDef.shape = new b2CircleShape(30/scale); //
var fixture = specialBody.CreateFixture(fixtureDef);
}
//实现接触监听器
function listenForContact(){
var listener = new Box2D.Dynamics.b2ContactListener;
//参数为接触和冲击力(法向和切向冲击力)
listener.PostSolve = function(contact, impulse){
var body1 = contact.GetFixtureA().GetBody();
var body2 = contact.GetFixtureB().GetBody();
//如果接触的两个物体都具有生命值,则减少其生命值
if (body1 == specialBody || body2 == specialBody){
var impulseAlongNormal = impulse.normalImpulses[0];
//生命值减少
specialBody.GetUserData().life -= impulseAlongNormal;
console.log("impulse:", impulseAlongNormal,
"life:", specialBody.GetUserData().life);
}
};
world.SetContactListener(listener);
}
//绘制自己的角色
function drawSpecialBody(){
//获取body的位置和角度
var position = specialBody.GetPosition();
var angle = specialBody.GetAngle();
//移动并旋转物体
context.translate(position.x*scale, position.y*scale);
context.rotate(angle);
//绘制实心的圆面
context.fillStyle = "rgb(200, 150, 250);";
context.beginPath();
context.arc(0,0,30,0.2*Math.PI,false);
context.fill();
//绘制两个矩形的眼睛
context.fillStyle = "rgb(255, 255, 255);";
context.fillRect(-15, -15, 10, 5);
context.fillRect(5, -15, 10, 5);
//绘制向上或向下的圆弧,根据生命值决定是否微笑
context.strokeStyle = "rgb(255, 255, 255);";
context.beginPath();
if (specialBody.GetUserData().life > 100){
context.arc(0,0,10,Math.PI,2*Math.PI,true);
} else {
context.arc(0,10,10,Math.PI,2*Math.PI,false);
}
context.stroke();
//移动并旋转坐标轴至最初的位置和角度
context.rotate(-angle);
context.translate(-position.x*scale, -position.y*scale);
}