2D物理引擎 Box2D for javascript Games 第一章
我要的是能在H5页面上跑的 javascript 版的 Box2D 啊!!!
最近想学习 Javascript 版本的 Box2D JS 物理引擎,无奈搜了半天也没找到相对比较系统的资料
官方网站也只是简单的介绍,找到一本别人翻译的 Box2d for Flash Games 。
没有 Javascrip t版本的啊。(υ◉ω◉υ)
我要的是能在 H5 页面上跑的 javascript 版本的教程啊!!!
So... ⋋(◍’Θ’◍)⋌
以前的我是 AS3 脚本程序出生, 那么唯有用我丢掉的近10年 AS3 脚本经验把它改写成Javascript版本的了
谁让我现在写的是Javascript呢。
看…我做的封面
完美!!!
用 Fireworks 把原来的封面做成了javascript,可见我功力了吧,请叫我美工殿下!
好吧,那就一边学,一边改成 javascript 版本了。
本系列源码持续更新中,已寄存在 github 上
https://github.com/willian12345/Box2D-for-Javascript-Games
开始前的一些说明
你必定假设你对javascript或者前端知识已经比较熟悉了,如果不熟悉的话你得先补一下前端知识再往下看
FLASH中的舞台对应网页中的Canvas
AS3 (ActionScript3.0)脚本对应网页中的Javascript
后期还会用到 createjs 用于渲染代替 Flash 中的 Graphics 类
2D物理引擎中的一些概念名词翻译列表
感觉翻译的不是很好,我就尽量按中文版与英文版都对照着用 Javascript 重写一篇这本书吧
rigid body: 刚体
fixture: 夹具
box: 盒子或矩形
debug draw: 调试绘图
density: 密度
friction: 摩擦或摩擦系数
restitution: 恢复或恢复系数
force: 力或作用力
impulse: 冲量
linear velocity : 线速度或线速率
joint: 关节
motor: 马达
bullet: 子弹
sensor: 感应器
目录
第一章 Hello Box2D World
- 定义Box2D世界
- 运行模拟
- 小结
第二章 向世界添加刚体
- 你的第一个模拟----一个球落地
- 创建一个圆形形形状
- 创建夹具
- 使用调试绘制测试你的模拟
- 创建矩形形状
- 不同的刚体类型----static, dynamic 和 kinematic
- 密度,摩擦和恢复
- 创建图腾破坏者的关卡
- 创建复合刚体
- 创建定向矩形
- 创建各种类型的凸多边形
- 小结
番外篇 Box2d 结合图形库实现渲染
第三章 刚体的交互
- 通过鼠标点击选择并销毁刚体
- 将自定义属性指定到刚体上
- 遍历刚体并获取它的属性
- 小结
第四章 将力作用到刚体上
- 苹果掉落,修正
- 力,冲量和线速率
- 应用冲量来得到线速度
- 应用力来获得线速度
- 将力应用到真实的游戏中
- 物理游戏不只是关于物理
- 放置物理小鸟
- 发射物理小鸟
- 小结
第五章 碰撞处理
- 碰撞检查
- Box2D内建的碰撞监听
- 将碰撞开始和结束输出到输出窗口
- 检测当你要解决碰撞和当你解决了碰撞
- 在图腾破坏者中检测神像坠落地面
- 在愤怒的小鸟中销毁砖块并消灭小猪
- 小结
第六章 关节和马达
- 拾取并拖拽刚体—鼠标关节
- 让刚体之间保持给定的距离—距离关节
- 使刚体绕一个点旋转—旋转关节
- 当愤怒的小鸟遇见粉碎城堡
- 通过马达控制关节
- 通过键盘控制马达
- 让一些刚体不要发生碰撞—碰撞过滤
- 将它们放在一起
- 小结
第七章
- 使用你自己的图像资源代替调试绘图
- 小结
第八章 子弹和传感器
- 感受隧道效应
- 阻止隧道效应—设置刚体为子弹
- 通过传感器检测接触,可以允许刚体重叠
第一章 Hello Box2D World
如果你想创建2D的物理驱动游戏与应用,Box2D是最佳的有效选择。
Box2D是一个 2D刚体的仿真库,它被使用在一些最成功的游戏上,例如在iPhone上的Angry Birds 和Tiny Wings或者在Flash上的Totem Destroyer和Red Remover。
Google一下它们,你 将会发现很多热心的评论。
在我们进入Box2D世界之前,让我说明一下什么是刚体(Rigid Bodies)。
它是一块非 常坚硬的物质,任何方法都不能使它弯曲。无论你怎样用力去撞击(Hit)或投掷 (Throw)它,都无法改变它的形状。
在真实世界中,你可以将它的硬度想象成钻 石,甚至比钻石还要硬。也许你可以展开想象,假设它是来至外空间的一块不可变形 的物质。
Box2D只管理刚体(Rigid Bodies),从现在起,我们将称它为刚体(Bodies)(这句 话中文是看不出什么不同的,英文将Rigid Bodies简称为Bodies),不用当心,你将还 可以模仿不是刚性的材料,例如弹性球体。
让我们看看,你将在本章节学习到那些知识:
• 安装Box2D
• 创建你的第一个Box2D世界
• 了解重力和睡眠刚体
• 运行程序,操作时间步和约束
本章结束后,你将能创建一个空的可运行的世界,在那里你可以搭建你那了不起的物理游戏。
在网页中安装Box2D
你可以从官方站点下载最新的Box2D.js版本
或在我的github上直接下载合并好的源码 https://github.com/willian12345/Box2D-for-Javascript-Games/
新建一个demo1-1.html页面
下载到 Box2D.js 版本后放在网页同级目录或其它目录
根据目录在网页中直接引用
<script src="Box2d.js"></script>
可比在比 FLASH 简单多了
网页中再添加
<script type="text/javascript"> function init(){ function main(){ console.log(Box2D); } main(); } init(); </script>
测试一把
在浏览器中打开 1.html
打开浏览器调试工具
应该能看到调试器的 console 内输出 Object {Collision: Object, Common: Object, Dynamics: Object}
init() 方法可以在 onload 时调用, init 方法内再建一个 main 方法
模拟 FLASH 中自动调用的 Main.as 类。这里需要手动调用。当然方法名你随意
完整代码在 demo1-1.html
中
第一步成功!
Box2D 的 “命名空间” 太长了,为了方便,在 init 方法开始处可以添加以下代码,方便使用
var b2Vec2 = Box2D.Common.Math.b2Vec2 ,b2AABB = Box2D.Collision.b2AABB ,b2BodyDef = Box2D.Dynamics.b2BodyDef ,b2Body = Box2D.Dynamics.b2Body ,b2FixtureDef = Box2D.Dynamics.b2FixtureDef ,b2Fixture = Box2D.Dynamics.b2Fixture ,b2World = Box2D.Dynamics.b2World ,b2MassData = Box2D.Collision.Shapes.b2MassData ,b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape ,b2CircleShape = Box2D.Collision.Shapes.b2CircleShape ,b2DebugDraw = Box2D.Dynamics.b2DebugDraw ,b2MouseJointDef = Box2D.Dynamics.Joints.b2MouseJointDef ;
先不用管具体做什么用,后面会一一用到的
当我给本节标题命名为 Hello Box2D World 时,我并不只是为了创建另一个 "Hello World" 程序,而是我想要介绍所有 Box2D 模拟和事件发生的环境: 世界(World)。
世界 (World) 是模拟发生的舞台。你想要通过 Box2D 物理引擎控制的所有事物必须在在 World 中。
幸运的是,Box2D World 拥有足够大的空间来容纳你需要的任何事物, 所以你无需担心 World 的边界 (boundaries)。
你只需记住在电脑中的任何事物都要受到某种限制。所以,越大的世界(World),将会 消耗你的电脑越多的资源去管理它。
定义 Box2D 世界
与现实世界一样,Box2D World 有重力 (gravity), 所以你需要先定义世界 重力 (world gravity)。
- 在你的 main 方法中,添加下面的一行代码:
var gravity = new b2Vec2(0,9.81);
这里将介绍我们的第一个Box2D数据类型:b2Vec2。(译者注:在javascript中可没这个数据类型,把它当成一个对象就好了)
b2Vec2 是一个2D的向量数据类型,它将储存 x 和 y 。如你所见,构造函数有两个参数,都是数值,代表了 x 和 y 分量。通过这种方法我们定义 gravity 变量作为一个矢量,它有 x=0 (这意味着水平的重力)和y=-9.81(这意 味着近似的地球重力)。
物理学中说过,一个物体在地球表面自由下落的加速度近似为9.81m/s^2(米 每平方秒)也可写作"m/s/s"。所以,假设没有任何空气阻力,我们在模拟一 个真实的世界(real-world)环境。解释物体下落的原理已经超越本书的范 围,但是你可以在Google或Wikipedia中搜索"equations for a falling body"去获得 更多的信息。- 设置接下来的这一行代码:
var gravity = new b2Vec2(0,1.63);
你也可以将参数设置为 (0, 0) 来模拟一个没有重力的环境:
var gravity = new b2Vec2(0,0);
- 我们还需要告诉世界,当世界中的刚体静止时,可以允许他们进入睡眠状态,这样它们将不受作用力的影响。
一个睡眠的刚体无需模拟,它只是表示自 己的存在,并静止在它的位置上,不会对世界中的任何事物产生影响,允许 Box2D忽略它,而且因此会提升处理速度以及让我们获得更好的性能。
所以推 荐可能时让刚体睡眠。 - 添加下面的一行,它只是一个简单的布尔(Boolean)变量定义:
var sleep = true;
- 最后,我们准备创建我们的第一个世界(world):
var world = new b2World(gravity,sleep);
- 现在我们有一个容器来管理所有的刚体并且执行我们的动态模拟。
- 让我们来简单的回顾一下之前的代码,此刻,你的代码应该看起如下面 所示:
<script type="text/javascript"> function init(){ var b2Vec2 = Box2D.Common.Math.b2Vec2 ,b2AABB = Box2D.Collision.b2AABB ,b2BodyDef = Box2D.Dynamics.b2BodyDef ,b2Body = Box2D.Dynamics.b2Body ,b2FixtureDef = Box2D.Dynamics.b2FixtureDef ,b2Fixture = Box2D.Dynamics.b2Fixture ,b2World = Box2D.Dynamics.b2World ,b2MassData = Box2D.Collision.Shapes.b2MassData ,b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape ,b2CircleShape = Box2D.Collision.Shapes.b2CircleShape ,b2DebugDraw = Box2D.Dynamics.b2DebugDraw ,b2MouseJointDef = Box2D.Dynamics.Joints.b2MouseJointDef ; var gravity = new b2Vec2(0, 9.81); var sleep = true; var world = new b2World(gravity, sleep); function main(){ } main(); } init(); </script>
现在你学习了怎样去创建并配置一个Box2D世界。让我们来看看你将怎样在它里面实 现物理效果的模拟 。
运行模拟
你需要在每一帧都进行模拟,所以首先你需要一个监听来触发每一帧
- 让我们添加一点代码:
<script> function init(){ var b2Vec2 = Box2D.Common.Math.b2Vec2 ,b2AABB = Box2D.Collision.b2AABB ,b2BodyDef = Box2D.Dynamics.b2BodyDef ,b2Body = Box2D.Dynamics.b2Body ,b2FixtureDef = Box2D.Dynamics.b2FixtureDef ,b2Fixture = Box2D.Dynamics.b2Fixture ,b2World = Box2D.Dynamics.b2World ,b2MassData = Box2D.Collision.Shapes.b2MassData ,b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape ,b2CircleShape = Box2D.Collision.Shapes.b2CircleShape ,b2DebugDraw = Box2D.Dynamics.b2DebugDraw ,b2MouseJointDef = Box2D.Dynamics.Joints.b2MouseJointDef ; var gravity = new b2Vec2(0, 9.81); var sleep = true; var world = new b2World(gravity, sleep); function main(){ setInterval(updateWorld, 1000 / 60); } function updateWorld() { console.log("my awesome simulation runs here"); } main(); } init(); </script>
- 没有什么新的,我们只是添加了一个 setInterval 循环定时执行,但是我们需要它有序的运行 updateWorld() 方法中的模拟。
Box2D 是通过模拟世界的 step time (译:可以理解为帧频)来进行模拟工作的。
这意味着世界将在每一个时间步被更新。
这将取决于我们在模拟中所采用的 step time。通常情况下,物理游戏的时间步为 1/60 秒。 - 下面是 updateWorld 函数的第一行:
var timeStep = 1/30
只是定义了 step time 还不够。在每一步, 每一个物理实体(physic entity)根据 作用于自身的作用力来更新(不包括睡眠状态)。
处理这项任务的算法叫 "约束解算器" 或 "约束求解器" (constraint solver)。
它是基于循环每一个约束然后解算来进的,一次一个,如果你想要学习更多的关于约束的知识,在google在搜 索"constraint algorithm"。
虽然单个约束可以被完美的解算,但是多个约束时,它 会搅乱之前已经解算的别的约束。
试想,当两个球移动的时候: 在真实的世界,每一个球的位置是在相同的时间更新。
在电脑的模拟中,我们需要通过循环来更新球的位置,而每次只能一个。
试想一下for循环每次遍历更新一个球。只要球彼此间没有发生 相互作用,一切正常运行,但是如何第二个球撞击了第一个球,谁的位置被更新了?它们会重叠,这在刚体模拟中是不可能的事情。
通过取合适的近似值来解决这个问题,我们需要循环所有的约束不止一次。现在问题是: 我们要循环多少次?
有两种约束解算器: 速率约束解算器(velocity constraint solver)和位置约束解算器(position constraint solver)。
速率约束解算器依据它们的在世界中的冲量来移动物理实体。
位置约束解算器调整物理实体的位置避免重叠。
所以,越高的便利次数,将会有更精确的模拟,但是性能会更低。我将设法处理超过 100 个物理实体设置分别设置位置 10 速率约束和位置约束解算器为 10 次迭代, Box2D 的作者推荐8次速率和 3 次位置遍历。
具体设置多少你自己来定。 - 与此同时,我将使用对两个约束解算器使用10次遍 历。 我们设置了两个新变量:
var velIterations:int = 10; var posIterations:int = 10;
- (译者注:先不用理解太深,不管这个“约束”先 (=◎ω◎=))
- 最后我们准备调用 world 变量的 step 方法来更新模拟。
在 updateWorld 方法中使用 world, 我们需要把 world 作为类变量声明,如下所 示:
<script> function init(){ var b2Vec2 = Box2D.Common.Math.b2Vec2 ,b2AABB = Box2D.Collision.b2AABB ,b2BodyDef = Box2D.Dynamics.b2BodyDef ,b2Body = Box2D.Dynamics.b2Body ,b2FixtureDef = Box2D.Dynamics.b2FixtureDef ,b2Fixture = Box2D.Dynamics.b2Fixture ,b2World = Box2D.Dynamics.b2World ,b2MassData = Box2D.Collision.Shapes.b2MassData ,b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape ,b2CircleShape = Box2D.Collision.Shapes.b2CircleShape ,b2DebugDraw = Box2D.Dynamics.b2DebugDraw ,b2MouseJointDef = Box2D.Dynamics.Joints.b2MouseJointDef ; var world; function main(){ var gravity = new b2Vec2(0, 9.81); var sleep = true; world = new b2World(gravity, sleep); setInterval(updateWorld, 1000 / 60); } function updateWorld() { var timeStep = 1/30; var velIterations = 10; var posIterations = 10; world.Step(timeStep,velIterations,posIterations); } main(); } init(); </script>
- 现在我们有了我们自己的世界配置,然后运行。不幸的是,这是一个非常空洞的世界,它的里面没有任何东西。
所以在下一章,我们将向世界中填充各种各样的物理实体。 - 最后还有一件事情,在每一步之后,你需要清除作用力,让模拟从 下一步再次开始。
你可以将这行world.ClearForces();
代码添加到step方法之后;你最后的代 码如下所示:
<script> function init(){ var b2Vec2 = Box2D.Common.Math.b2Vec2 ,b2AABB = Box2D.Collision.b2AABB ,b2BodyDef = Box2D.Dynamics.b2BodyDef ,b2Body = Box2D.Dynamics.b2Body ,b2FixtureDef = Box2D.Dynamics.b2FixtureDef ,b2Fixture = Box2D.Dynamics.b2Fixture ,b2World = Box2D.Dynamics.b2World ,b2MassData = Box2D.Collision.Shapes.b2MassData ,b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape ,b2CircleShape = Box2D.Collision.Shapes.b2CircleShape ,b2DebugDraw = Box2D.Dynamics.b2DebugDraw ,b2MouseJointDef = Box2D.Dynamics.Joints.b2MouseJointDef ; var world; function main(){ var gravity = new b2Vec2(0, 9.81); var sleep = true; world = new b2World(gravity, sleep); setInterval(updateWorld, 1000 / 60); } function updateWorld() { var timeStep = 1/30; var velIterations = 10; var posIterations = 10; world.Step(timeStep,velIterations,posIterations); world.ClearForces(); // 清除作用力 } main(); } init(); </script>
- 源码全部代码在 github上的
demo1-1.html
中,可查看运行
小结
你刚刚学习了怎样为在网页中安装使用Box2D。将它包含到你的项目中并运行,重力规则模拟, 管理时间步以及约束解算器。
到现在为止,你的网页上其实并没有显示任何东西。
你有一个空的世界,准备成为你游戏发生的容器。保存它然后在每一个未来的项目中使用它!