文章目錄
  1. 1. Forward
  2. 2. 第一步,了解Box2D基础
    1. 2.1. 引入Box2D
    2. 2.2. 定义World变量
    3. 2.3. 添加第一个物体:地面
    4. 2.4. 绘制世界:调试绘图模式
    5. 2.5. 动画
  3. 3. Animation Pro

Forward

(源码来自 <<Pro HTML5 Games>> 第3章内容,一本国内相对冷门的书

适用对象:一切有关物理计算的游戏

准备工作:

  • 库:Box2dWeb-2.1.a.3.min.js
    这个Box2D库是相当一大部分库的基础和实现,由C++编写
  • 浏览器:Chrome,FireFox等
    不推荐使用Safari,渲染速度肉眼可见慢于Chrome,容易崩溃
  • 基础知识:JavaScript基础即可,C艹er请绕路

无论如何,

第一步,了解Box2D基础

引入Box2D

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<head lang="en">
<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"></script>
<script src="box2d.js" type="text/javascript" charset="utf-8"></script>
</head>
<body onload="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中,我们为了方便,将最常用的对象定义为快捷变量

1
2
3
4
5
6
7
8
9
10
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变量

创建world对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var world;
var scale = 30; // 在canvas上的30像素表示Box2D世界的1米
function init(){
// 创建Box2D world对象,该对象将完成大部分物理计算
var gravity = new b2Vec2(0, 9.8); // 声明重力加速度为9.8m/s²,方向向下
var allowSleep = true; // 允许静止的物体进入休眠状态,休眠物体不参与物理仿真计算
world = new b2World(gravity, allowSleep);
/*-----------------------------------TO DO:------------------------------------*/
createFloor();
// 创建一些具有简单性状的物体
createRectangularBody();
createCircularBody();
createSimplePolygonBody();
// 创建由两个形状组成的物体
createComplexBody();
// 将两个物体用转动关节连接起来
createRevoluteJoint();
// 创建具有用户自定义数据的对象
createSpecialBody();
// 创建接触监听器并追踪事件
listenForContact();
setupDebugDraw();
animate();
/*-----------------------------------------------------------------------------------*/
}

解释:b2World对象是Box2D的核心,创建时接受一个向量参数b2Vect作为重力数值,和一个布尔值决定允许静止物体休眠(以灰色表示,不参与计算从而提升性能)

添加第一个物体:地面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//创建地面--第一个物体
function createFloor(){
// body的预定以对象,包含创建body刚体需要用到的所有数据
var bodyDef = new b2BodyDef();
bodyDef.type = b2Body.b2_staticBody; //静止物体
bodyDef.position.x = 640/2/scale; //中心x坐标
bodyDef.position.y = 450/scale; //中心y坐标
// 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); //物体有了自己的属性辣,此时才会显现物体
}

(卧槽好麻烦)创建物体时,需要相当多的信息,我们只是创建一个长条矩形就写了这么多行。
总的来说,要创建一个物体,需要给物体套上一个fixture,在给world套上这个物体。

绘制世界:调试绘图模式

要绘制图形,就要和canvas绑定,下面是绑定的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//设置调试绘图
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);
}

总的来说,要给world套上一个绘图模式,这样world世界里的东西,才能以一定参数修改后显示在canvas上。

动画

动画相当重要,Box2D需要我们每一帧都先要清除作用力,然后再更新数据。最后别忘了重复调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var timeStep = 1/60;
// 按照Box2D手册给的建议的迭代数,速度是8,位置是3
var velocityIterations = 8;
var positionIterations = 3;
function animate(){
// 时间步长,速度迭代数,位置迭代数
world.Step(timeStep, velocityIterations, positionIterations);
world.ClearForces();
world.DrawDebugData();
/*-----------------------------------TO DO:------------------------------------*/
// 自定义绘制
if (specialBody) {
drawSpecialBody();
}
//摧毁耗尽生命值的物体
if (specialBody && specialBody.GetUserData().life <= 0) {
world.DestroyBody(specialBody);
specialBody = undefined;
console.log("The special body was destroyed");
}
/*---------------------------------------------------------------------------------*/
setTimeout(animate, timeStep);
}

world.step()的三个参数决定了积分算法的尺度,而一般物理引擎效果较好时,时间间隔至少为60HZ或者1/60S,而且需要恒定,这样才能每次产生相同的结果,便于调试。

这里XANA不自觉地想到了requestAnimationFrame这样一个东西,这是一个可以用来测试浏览器性能的东西,而且具有标签页暂停的逆天功能。因为我们知道,固定帧数的动画有个缺点,太大可能会导致浏览器卡崩,并且setInterval具有若干缺点。而如果根据浏览器对重复调用方法的调用时间触发一个timeout,从而循环调用这个方法,可以有效利用浏览器(队列)资源。到后期我们可以根据这个函数改进这个方法,当然这是题外话,与本例无关。至此本文结束。

Animation Pro

首先,若requestAnimationFrame不存在,则重写之。从中你也能看出此函数的作用机理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(function(){
var lastTime = 0;
var vendors = ['ms', 'mos', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame;++x){
window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
window.cancelAnimationFrame =
window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function (callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function(){
callback(currTime + timeToCall);
}, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame){
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
}
}
})();

重写后的animate,根据响应速度来设置世界时间下一步的步长,主动截断超过1/30秒的步长

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var lastUpdateTime;
function animate(){
var currentTime = new Date().getTime();
var timeStep;
if (lastUpdateTime){
timeStep = (currentTime - lastUpdateTime)/1000;
if(timeStep > 2/60){
timeSetup = 2/60;
};
}
lastUpdateTime = currentTime;
// 要执行的绘制部分
window.requestAnimationFrame(animate, canvas);
}

文章目錄
  1. 1. Forward
  2. 2. 第一步,了解Box2D基础
    1. 2.1. 引入Box2D
    2. 2.2. 定义World变量
    3. 2.3. 添加第一个物体:地面
    4. 2.4. 绘制世界:调试绘图模式
    5. 2.5. 动画
  3. 3. Animation Pro