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; function init(){ var gravity = new b2Vec2(0, 9.8); var allowSleep = true; world = new b2World(gravity, allowSleep); 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(){ var bodyDef = new b2BodyDef(); bodyDef.type = b2Body.b2_staticBody; bodyDef.position.x = 640/2/scale; bodyDef.position.y = 450/scale; 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); 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(); debugDraw.SetSprite(context); debugDraw.SetDrawScale(scale); debugDraw.SetFillAlpha(0.3); debugDraw.SetLineThickness(1.0); 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; var velocityIterations = 8; var positionIterations = 3; function animate(){ world.Step(timeStep, velocityIterations, positionIterations); world.ClearForces(); world.DrawDebugData(); 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); }
|