Box2D 是一个用于游戏的 2D 刚体模拟库。程序员可以在自己的游戏中使用它,让物体以逼真的方式移动,使游戏世界更具互动性。从游戏引擎的角度来看,物理引擎只是一个程序动画系统。
Box2D 是用可移植的 C++ 编写的。引擎中定义的大多数类型都以 b2 前缀开头。希望这足以避免与您的游戏引擎名称冲突
先决条件
在本手册中,我假设您熟悉基本的物理概念,如质量、力、扭矩和脉冲。如果不熟悉,请先查阅 Google 搜索和维基百科。
Box2D 是游戏开发者大会物理教程的一部分。您可以从 box2d.org 的下载区获取这些教程。
由于 Box2D 是用 C++ 编写的,因此希望您有 C++ 编程的经验。Box2D 不应该是您的第一个 C++ 编程项目!您应该熟悉编译、链接和调试。
注意: Box2D 不应该是您的第一个 C++ 项目。在使用 Box2D 之前,请先学习 C++ 编程、编译、链接和调试。网上有很多这方面的资源。
使用范围
本手册涵盖了 Box2D API 的大部分内容。但并非每个方面都涵盖在内。请查看 Box2D 附带的测试平台以了解更多信息。
本手册仅随新版本更新。Box2D 的最新版本可能与本手册不同步。
核心概念
Box2D 使用几个基本概念和对象。我们在此对这些对象进行简要定义,更多细节将在本文档后面给出。
形状
形状是二维几何对象,如圆或多边形。
刚体
刚体是指非常坚固的物质块,该物质块上任何两个物质位之间的距离都是恒定的。它们像钻石一样坚硬。在下面的讨论中,我们将交替使用 "body "和 "rigid body"。
夹具(fixture)
夹具将形状与物体绑定,并添加密度、摩擦力和回复力等材料属性。夹具将形状放入碰撞系统(宽相位),使其可以与其他形状碰撞。
约束
约束是一种物理连接,可以消除物体的自由度。一个二维体有 3 个自由度(两个平移坐标和一个旋转坐标)。如果我们将一个物体固定在墙上(就像钟摆一样),就等于将物体约束在墙上。此时,物体只能围绕销轴旋转,因此约束消除了 2 个自由度。
接触约束
一种特殊约束,用于防止刚体穿透并模拟摩擦和恢复。您无需创建接触约束;它们由 Box2D 自动创建。
关节
这是一种用于将两个或多个体固定在一起的约束。Box2D 支持多种关节类型:外旋、棱柱、距离等。某些关节可能具有限制和电机。
关节限位
关节限位限制了关节的活动范围。例如,人的肘部只允许一定的角度范围。
关节马达
关节马达根据关节的自由度驱动连接体运动。例如,可以使用电机驱动肘部旋转。
世界
物理世界是一起互动的机构、夹具和约束的集合。Box2D 支持创建多个世界,但通常没有必要也不希望这样做。
求解器
物理世界有一个求解器,用于推进时间并解决接触和关节约束。Box2D 求解器是一个高性能迭代求解器,以 N 次顺序运行,其中 N 是约束的数量。
连续碰撞
求解器使用离散的时间步长在时间上推进物体。如果不进行干预,可能会导致隧道效应
Box2D 包含处理隧道问题的专门算法。首先,碰撞算法可以对两个物体的运动进行内插,以找到首次撞击时间 (TOI)。其次,有一个子步进求解器可以将物体移动到它们的首次撞击时间,然后解决碰撞问题。
作者注:隧道效应是一种现象,当我们在游戏或模拟中让物体移动得非常快时,有时候它们会穿过墙壁或其他物体,而不会发生碰撞。就像一个超快的小球直接穿过一堵墙一样,而不是像平常那样撞到墙上停下来。这是因为计算机每次移动物体时,会把时间分成一个个小片段(我们称它们为“时间步”),但如果物体移动得太快,它可能会在一个时间步内跨过墙壁的位置,所以看起来就像穿透了墙。
为了避免这种问题,Box2D 这类的物理引擎会使用一些特殊的方法来检查物体在移动过程中是否会碰到其他物体。如果它发现两个物体可能会碰撞,它会找出它们第一次碰撞的准确时间点,并且把物体移动到那个时间点再处理碰撞。这样就能避免物体“隧道”穿过其他物体,确保游戏里的物理效果更真实。
简单来说,隧道效应就是物体移动太快而没有正常碰撞,但物理引擎有办法避免这种不自然的现象。
模块
Box2D 由三个模块组成: 通用模块、碰撞模块和动力学模块。通用模块包含分配、数学和设置的代码。碰撞模块定义了形状、宽相位和碰撞函数/查询。最后,动态模块提供了模拟世界、体、夹具和关节。
单位
Box2D使用浮点数,并且需要使用公差来使其性能表现良好。这些公差已经调整得很好,以便与米-千克-秒(MKS)单位制配合良好。特别是,Box2D已经调整得很好,以便在0.1到10米之间移动形状时表现良好。因此,这意味着尺寸在罐头和公交车之间的对象应该能够很好地工作。静态形状的长度可以长达50米而没有问题。
作为一个2D物理引擎,使用像素作为单位是很诱人的。不幸的是,这将导致较差的模拟性能,可能会出现奇怪的行为。200像素长的对象在Box2D中将被视为一个45层楼高的建筑物。
注意:Box2D是针对MKS单位进行调整的。保持移动对象的尺寸大致在0.1到10米之间。在渲染环境和角色时,您需要使用一些缩放系统。Box2D测试平台通过使用OpenGL视口转换来实现这一点。请不要使用像素单位。
这段话在讲一个叫 Box2D 的物理引擎,它用来模拟物体的运动和碰撞,就像你在游戏里看到的那样。
1. 什么是公差(Tolerances)?
公差就像是一种“允许的误差范围”。在数学计算中,有时候我们不可能得到完全准确的结果,而只能得到一个非常接近的结果。这种允许我们不那么精确的范围就叫做公差。Box2D 里的公差已经调好了,这样它在处理物体的碰撞时会非常顺利,不会出错。
2. 为什么要用特定的单位(MKS单位)?
Box2D 使用的是 MKS 单位,这个缩写代表**米(Meter)**、**千克(Kilogram)**和**秒(Second)**。所以,它是按照米(长度)、千克(质量)和秒(时间)来处理物体的运动。
- 米(Meter)是用来表示长度的单位,比如一米差不多是一个小朋友的身高。
- 千克(Kilogram)是用来表示重量的单位,比如一千克相当于一瓶水的重量。
- 秒(Second)是用来表示时间的单位,比如一秒就是你轻轻眨一次眼睛的时间。
3. 为什么不能用像素(Pixels)?
像素是计算机屏幕上最小的点,用它来表示图像。如果你在游戏中用像素来表示物体的大小,比如一个物体有200个像素长,Box2D 会以为这个物体非常巨大,像是一栋45层高的大楼。这就会让物理引擎的计算变得不准确,出现奇怪的现象。
4. 应该用什么单位?
你应该使用 MKS 单位里的**米**来表示长度,而不是用像素。Box2D 最适合处理大小在 0.1 到 10 米之间的物体,这些物体的大小就像从一个汤罐头(小物体)到一辆大巴车(大物体)那么大。
5. 如何把像素转化为米?
你可以做一个“缩放”步骤。比如,如果你在游戏中有一个物体,它在屏幕上是200个像素长,你可以决定把1米等于100个像素。这样的话,这个物体在 Box2D 里就会被认为是2米长(200像素 ÷ 100像素/米 = 2米),而不是非常巨大。
6. 总结
简单来说,就是 Box2D 最适合处理大小在0.1米到10米之间的物体,不要直接用像素来表示大小,而是用米这样的单位来测量。你可以通过一个比例(比如1米等于100像素)来转换物体的尺寸,让物理引擎正确处理物体的运动和碰撞。
最好将 Box2D 刚体视为移动的广告牌,您可以在其上添加艺术作品。广告牌可能以 "米 "为单位进行移动,但您可以通过一个简单的缩放因子将其转换为像素坐标。然后就可以使用这些像素坐标来放置精灵等。您还可以考虑翻转坐标轴。
另一个需要考虑的限制是整个世界的大小。如果您的世界单位大于 2 千米左右,那么失去的精度可能会影响稳定性。
注意: Box2D 在世界大小小于 2 千米时效果最佳。使用 b2World::ShiftOrigin 可支持更大的世界。
如果您需要一个更大的游戏世界,请考虑使用 b2World::ShiftOrigin 使世界原点靠近玩家。我建议使用网格线和一些滞后来触发对 ShiftOrigin 的调用。这种调用不应频繁进行,因为它会消耗 CPU 资源。在游戏单位和 Box2D 单位之间转换时,您可能需要存储物理偏移。
Box2D 使用弧度表示角度。肢体旋转以弧度存储,并可能无限制地增长。如果角度的大小变得过大,请考虑对体的角度进行归一化处理(使用 b2Body::SetTransform)。
注意 Box2D 使用弧度,而不是度数。
更改长度单位
高级用户可以修改 b2_lengthUnitsPerMeter 来更改长度单位。通过定义 B2_USER_SETTINGS 并提供 b2_user_settings.h,可以避免合并冲突。详情请参见 b2_settings.h 文件。
工厂和定义
快速内存管理在 Box2D API 的设计中起着核心作用。因此,当您创建 b2Body 或 b2Joint 时,需要调用 b2World 上的工厂函数。切勿尝试以其他方式分配这些类型。
有以下创建函数
b2Body* b2World::CreateBody(const b2BodyDef* def)
b2Joint* b2World::CreateJoint(const b2JointDef* def)
还有相应的销毁函数
void b2World::DestroyBody(b2Body* body)
void b2World::DestroyJoint(b2Joint* joint)
创建主体或关节时,您需要提供一个定义。这些定义包含构建主体或关节所需的所有信息。通过使用这种方法,我们可以防止构造错误,减少函数参数的数量,提供合理的默认值,并减少访问器的数量。
由于夹具(形状)必须是主体的父对象,因此它们是通过 b2Body 上的工厂方法创建和销毁的:
b2Fixture* b2Body::CreateFixture(const b2FixtureDef* def)
void b2Body::DestroyFixture(b2Fixture* fixture)
也有直接从形状和密度创建夹具的快捷方式。
b2Fixture* b2Body::CreateFixture(const b2Shape* shape, float density)
工厂不会保留对定义的引用。因此,您可以在堆栈中创建定义,并将其保存在临时资源中。