Box2D 简介
Box2D 是一个模拟【刚体】运动的物理引擎,而【刚体】指的就是不会发生形变的固体,没错…也就是说模拟起流体来你需要另辟蹊跷。
除了用小颗粒来模拟流体的效果,用基于 Box2D 的 LiquidFun 也可以模拟流体效果,不过我们暂时不关心这件事,这只是为了强调【刚体】的概念而已。
Box2D 好处都有啥?
显而易见的,Box2D 处理了所有复杂的物理过程计算,转而留给我们这些简单易用的 API,不过还有这些地方:
高效的性能,Box2D 在节约性能方面可谓是耗费了一番心思,甚至内存回收方面使用的是自己实现的内存池
没有用到一个 STL 容器,作者称其为
"highly portable C++"只负责物理数据的计算而不负责渲染,非常方便集成到你的游戏或游戏引擎中,且与 Box2D 相关的所有名称前缀都带有 “b2” 以防止与你项目中的名称冲突
那接下来,就先从介绍 Box2D 的一些基础概念开始吧。
Box2D 概念介绍
世界 (world)
Box2d 的世界是一个
【刚体】、【夹具】、【约束】相互作用的集合,且这些对象的内存管理都由 Box2D 自己建立的内存池来管理。由于世界也是一个对象,创建多个世界是允许的。刚体 (rigid body)
物体上任意两点之间的距离不会发生改变的物体,也就是说这个物体是绝对不会发生形变的。
形状 (shape)
一种 2D 的几何物体,比如说圆和多边形。
夹具 (fixture)
夹具能够将形状绑定到刚体上,并且附带材质属性,例如
【密度】、【摩擦力】、【恢复力】。夹具能够让形状加入到碰撞系统中(粗略阶段)来与其他形状进行碰撞。约束 (constraint)
约束是移除刚体的
【自由度】的物理连接,每一个 2D 物体都有三个自由度(x,y轴上的两个平移坐标和一个旋转坐标)。如果我们将一个刚体钉在墙上的话(就像大摆钟那样),那么这个刚体便被移除了两个自由度(x,y轴上的平移自由),只拥有围绕着那根钉子转的自由。碰撞约束 (contact constraint)
为了防止刚体穿墙、模拟摩擦力和恢复力而设计的一种特别的约束,你并不需要手动创建它们,Box2D 会自动帮你创建。
关节 (joint)
一种用于将两个刚体连接在一起的约束,Box2D 有很多不同种类的关节,例如
【旋转】、【棱柱】、【距离】等等。有些关节可能还有【限制】和【马达】。关节限制 (joint limit)
关节限制约束了一个关节能够运动的范围,譬如滑槽中的滑轮只能在滑槽的槽里运动,人类的手臂也只允许一定范围内的转动。
关节马达 (joint motor)
关节马达根据关节的自由度来驱动被连接的刚体的运动,譬如汽车的发动机驱动了轮胎在车轴限制上的旋转,蒸汽机驱动了老式火车在轨道限制上的运动。
解算器 (solver)
解算器在物理世界中用于推进时间、求解碰撞和关节约束带来的影响。Box2D 的解算器是一个顺序执行 N 次的高效的迭代解算器,其中 N 是世界中约束的数量。
连续碰撞 (continuous collision)
因为解算器是在离散的时间上推进物体的运动的,所以当物体的速度过快时,有可能出现物体穿墙的现象,我们称其为
【隧道效应(tunneling)】,而 Box2D 有专门处理隧道效应的算法。首先,这种碰撞算法可以在两个形状的旧位置和新位置之间插值,找到它们第一次碰撞的时间 (time of impact, TOI);然后,碰撞解算器会将两个物体移动到它们第一次碰撞的这个插值时间上,求解它们之间的碰撞。
Box2D 的模块
Box2D 由三个模块构成,它们分别是 【通用模块(Common)】、【碰撞模块(Collision)】和 【动力学模块(Dynamics)】。
通用模块
最基本的模块,包含内存分配、数学和参数设置的代码
碰撞模块
在通用模块的基础上实现而来,定义了形状、粗略阶段和碰撞函数(或碰撞查询)
动力学模块
在通用模块和碰撞模块的共同基础上实现而来,提供了模拟的世界、刚体、夹具和关节
Box2D 的单位
Box2D 使用浮点数进行工作,而且为了让 Box2D 表现良好必须使用一套度量单位,于是乎 Box2D 便使用了 【米-千克-秒(meters-kilogram-second, MKS)】 的单位制。特别的是 Box2D 经过一系列调整,使得它能够很好地处理 0.1m - 10m 的移动中的形状,所以能动的对象小到罐头、大到公交车,Box2D 处理的都不错,而静态的对象长到 50m 都不是问题。
制作一个 2D 物理引擎的时候,你很容易将像素作为你的单位(我就这么干过)。不幸的是,这样不仅会导致最终模拟出来的效果不好,还可能会导致某些怪异的行为。在 Box2D 中,一个 200 格像素高的刚体将被视作有 45 层楼这么高!
值得注意的是,Box2D 针对 MKS 单位进行过一些调整,将移动对象的大小大约保持在了 0.1m-10m 之间,而当你渲染的时候可能需要使用到一些缩放系统,比如 Box2D 的 testbed 中就使用到了 OpenGL 的视口变换来做到了这一点。
切记的是,千万不要使用像素作为你的单位!
你可以将 Box2D 中的所有刚体当做一个会移动的广告牌,而这些广告牌上附带着你加给他们的贴图。这些广告牌在 Box2D 中以米为单位进行移动,但你可以用一些简单的缩放因数将它们转换成为你引擎中的像素坐标,并且用这些像素坐标来渲染屏幕中的对象。
整个世界的大小也有一定的限制,如果你的世界大于 2000m,那么损失的精度可能会影响到这个世界的稳定性。
刚才那一条的意思就是说,你的世界大小最好在 2000m 以内。如果有特别的需要的话,请使用 b2World::ShiftOrigin 来支持更大的世界。
b2World::ShiftOrigin 是改变世界坐标原点的函数,你应该让世界的坐标原点尽可能的靠近你的玩家。使用这个函数具有一定的 CPU 开销,所以不建议你进行频繁的调用。你可能需要储存一些偏移值转化 Box2D 坐标和你游戏中的坐标来实现更大的世界。
Box2D 使用弧度单位制,刚体的旋转单位都是弧度。因为弧度可能会变得非常大,所以你需要考虑用 b2Body::SetTransform 来标准化你的弧度。
工厂(Factories) 和 定义(Definitions)
由于 Box2D 拥有自己的内存池,且 Box2D 的快速内存管理是其核心之一,所以当你需要创建 刚体(b2Body) 和 关节(b2Joint) 等对象时,你需要调用 b2World 的工厂函数来为这些对象分配内存。千万不要尝试用其它方法给这些对象分配内存。
这些世界中的对象的内存管理有以下步骤:
创建一个对象的定义 (def),定义中包含有关于这个对象的信息
将定义传给 Box2D 的构造函数,让 Box2D 根据信息构造对象并为其分配内存
最后用 Box2D 的析构函数释放这些对象的资源,防止内存泄露