logo资料库

Box2D 中文手册.pdf

第1页 / 共31页
第2页 / 共31页
第3页 / 共31页
第4页 / 共31页
第5页 / 共31页
第6页 / 共31页
第7页 / 共31页
第8页 / 共31页
资料共31页,剩余部分请下载后查看
Box2D v2.0.1 用户手册
1. 导言
1.1 关于
1.2 必备条件
1.3 核心概念
2. Hello Box2D
2.1 创建一个世界
2.2 创建一个地面盒
2.3 创建一个动态物体
2.4 模拟(Box2D 的)世界
2.5 清理工作
2.6 关于 Testbed
3. API 设计
3.1 内存管理
3.2 工厂和定义
3.3 单位
3.4 用户数据
3.5 C++ 相关面
3.6 稻草人
4. 世界
4.1 关于
4.2 创建和摧毁一个世界
4.3 使用一个世界
4.3.1 模拟
4.3.2 扫描世界
4.3.3 AABB 查询
5. 物体
5.1 关于
5.2 物体定义
5.2.1 质量性质
5.2.2 位置和角度
5.2.3 阻尼
5.2.4 休眠参数
5.2.5 子弹
5.3 物体工厂
5.4 使用物体
5.4.1 质量数据
5.4.2 状态信息
5.4.3 位置和速度
5.4.4 力和冲量
5.4.5 坐标转换
5.4.6 列表
6 形状
6.1 关于
6.2 形状定义
6.2.1 摩擦和恢复
6.2.2 密度
6.2.3 筛选
6.2.4 传感器
6.2.5 圆形定义
6.2.6 多边形定义
6.3 形状工厂
6.4 使用形状
7 关节
7.1 关于
7.2 关节定义
7.2.1 距离关节
7.2.2 旋转关节
7.2.3 移动关节
7.2.4 滑轮关节
7.2.5 齿轮关节
7.2.6 鼠标关节
7.3 关节工厂
7.4 使用关节
7.4.1 使用距离关节
7.4.2 使用旋转关节
7.4.3 使用移动关节
7.4.4 使用滑轮关节
7.4.5 使用齿轮关节
7.4.6 使用鼠标关节
8 接触
8.1 关于
8.2 接触监听器
8.3 接触筛选
9 杂项
9.1 世界边界
9.2 隐式摧毁
10 设置
10.1 关于
10.2 公差
10.3 内存分配
11 调试绘图
简体中文翻译信息
Box2D v2.0.1 用户手册 原文:Box2D v2.0.2 User Manual 译者:Aman JIANG(江超宇),翻译信息。 1. 导言 1.1 关于 Box2D 是一个用于游戏的 2D 刚体仿真库。程序员可以在他们的游戏里使用它,它可以使物体的运动 更加可信,让世界看起来更具交互性。从游戏的视角来看,物理引擎就是一个程序性动画(procedural animation)的系统,而不是由动画师去移动你的物体。你可以让牛顿来做导演。 Box2D 是用可移植的 C++ 来写成的。引擎中定义的大部分类型都有 b2 前缀,希望这能消除它和你 游戏引擎之间的名字冲突。 1.2 必备条件 在此,我假定你已经熟悉了基本的物理学概念,例如质量,力,扭矩和冲量。如果没有,请先考虑读 一下 Chris Hecker 和 David Baraff (google 这些名字)的那些教程,你不需要了解得非常细致,但他们 可以使你很好地了解一些基本概念,以便你使用 Box2D。 Wikipedia 也是一个极好的物理和数学知识的获取源,在某些方面它可能比 google 更有用,因为它 的内容经过了精心的整理。 这不是必要的,但如果你好奇 Box2D 内部是如何工作的,你可以看 这些文档。 因为 Box2D 是使用 C++ 写成的,所以你应该具备 C++ 程序设计的经验,Box2D 不应该成为你的 第一个 C++ 程序项目。你应该已经能熟练地编译,链接和调试了。 1.3 核心概念 Box2D 中有一些基本的对象,这里我们先做一个简要的定义,在随后的文档里会有更详细的描述。 刚体(rigid body) 一块十分坚硬的物质,它上面的任何两点之间的距离都是完全不变的。它们就像钻石那样坚硬。在后 面的讨论中,我们用物体(body)来代替刚体。 形状(shape) 一块严格依附于物体(body)的 2D 碰撞几何结构(collision geometry)。形状具有摩擦(friction)和恢 复(restitution)的材料性质。 约束(constraint)
一个约束(constraint)就是消除物体自由度的物理连接。在 2D 中,一个物体有 3 个自由度。如果我 们把一个物体钉在墙上(像摆锤那样),那我们就把它约束到了墙上。这样,此物体就只能绕着这个钉子旋 转,所以这个约束消除了它 2 个自由度。 接触约束(contact constraint) 一个防止刚体穿透,以及用于模拟摩擦(friction)和恢复(restitution)的特殊约束。你永远都不必创建 一个接触约束,它们会自动被 Box2D 创建。 关节(joint) 它是一种用于把两个或多个物体固定到一起的约束。Box2D 支持的关节类型有:旋转,棱柱,距离等 等。关节可以支持限制(limits)和马达(motors)。 关节限制(joint limit) 一个关节限制(joint limit)限定了一个关节的运动范围。例如人类的胳膊肘只能做某一范围角度的运 动。 关节马达(joint motor) 一个关节马达能依照关节的自由度来驱动所连接的物体。例如,你可以使用一个马达来驱动一个肘的 旋转。 世界(world) 一个物理世界就是物体,形状和约束相互作用的集合。Box2D 支持创建多个世界,但这通常是不必要 的。 2. Hello Box2D 2.1 创建一个世界 每个 Box2D 程序都将从一个世界对象(world object)的创建开始。这是一个管理内存,对象和模拟的 中心。 要创建一个世界对象,我们首先需要定义一个世界的包围盒。Box2D 使用包围盒来加速碰撞检测。尺 寸并不关键,但合适的尺寸有助于性能。这个包围盒过大总比过小好。 b2AABB worldAABB; worldAABB.lowerBound.Set(-100.0f, -100.0f); worldAABB.upperBound.Set(100.0f, 100.0f); • 注意:worldAABB 应该永远比物体所在的区域要大,让 worldAABB 更大总比太小要好。如果一 个物体到达了 worldAABB 的边界,它就会被冻结并停止模拟。 接下来我们定义重力矢量。是的,你可以使重力朝向侧面(或者你只好转动你的显示器)。并且,我们 告诉世界(world)当物体停止移动时允许物体休眠。一个休眠中的物体不需要任何模拟。 b2Vec2 gravity(0.0f, -10.0f); bool doSleep = true;
现在我们创建世界对象。通常你需要在堆(heap)上创建世界对象,并把它的指针保存在某一结构中。 然而,在这个例子中也可以在栈上创建。 b2World world(worldAABB, gravity, doSleep); 那么现在我们有了自己的物理世界,让我们再加些东西进去。 2.2 创建一个地面盒 物体通常由以下步骤来创建: 1. 使用位置(position),阻尼(damping)等定义一个物体 2. 使用世界对象创建物体 3. 使用几何结构,摩擦,密度等定义形状 4. 在物体上创建形状 5. 可选地调整物体的质量以和附加的形状相匹配 第一步,我们创建地面体。要创建它我们需要一个物体定义(body definition),通过物体定义我们来 指定地面体的初始位置。 b2BodyDef groundBodyDef; groundBodyDef.position.Set(0.0f, -10.0f); 第二步,将物体定义传给世界对象来创建地面体。世界对象并不保存到物体定义的引用。地面体是作 为静态物体(static body)创建的,静态物体之间并没有碰撞,它们是固定的。当一个物体具有零质量的 时候 Box2D 就会确定它为静态物体,物体的默认质量是零,所以它们默认就是静态的。 b2Body* ground = world.CreateBody(&groundBodyDef); 第三步,我们创建一个地面的多边形定义。我们使用 SetAsBox 简捷地把地面多边形规定为一个盒子 (矩形)形状,盒子的中点就位于父物体的原点上。 b2PolygonDef groundShapeDef; groundShapeDef.SetAsBox(50.0f, 10.0f); 其中,SetAsBox 函数接收了半个宽度和半个高度,这样的话,地面盒就是 100 个单位宽(x 轴)以及 20 个单位高(y 轴)。Box2D 已被调谐使用米,千克和秒来作单位,所以你可以用米来考虑长度。然而, 改变单位系统是可能的,随后的文档中会有讨论。 在第四步中,我们在地面体上创建地面多边形,以完成地面体。 groundBody->CreateShape(&groundShapeDef); 重申一次,Box2D 并不保存到形状或物体的引用。它把数据拷贝到 b2Body 结构中。 注意每个形状都必须有一个父物体,即使形状是静态的。然而你可以把所有静态形状都依附于单个静 态物体之上。这个静态物体之需求是为了保证 Box2D 内部的代码更具一致性,以减少潜在的 bug 数
量。 可能你已经注意到了,大部分 Box2D 类型都有一个 b2 前缀。这是为了降低它和你的代码之间名字冲 突的机会。 2.3 创建一个动态物体 现在我们已经有了一个地面体,我们可以使用同样的方法来创建一个动态物体。除了尺寸之外的主要 区别是,我们必须为动态物体设置质量性质。 首先我们用 CreateBody 创建物体。 b2BodyDef bodyDef; bodyDef.position.Set(0.0f, 4.0f); b2Body* body = world.CreateBody(&bodyDef); 接下来我们创建并添加一个多边形形状到物体上。注意我们把密度设置为 1,默认的密度是 0。并 且,形状的摩擦设置到了 0.3。形状添加好以后,我们就使用 SetMassFromShapes 方法来命令物体通 过形状去计算其自身的质量。这暗示了你可以给单个物体添加一个以上的形状。如果质量计算结果为 0, 那 么 物 体 会 变 成 真 正 的 静 态 。 物 体 默 认 的 质 量 就 是 零 , 这 就 是 为 什 么 我 们 无 需 为 地 面 体 调 用 SetMassFromShapes 的原因。 b2PolygonDef shapeDef; shapeDef.SetAsBox(1.0f, 1.0f); shapeDef.density = 1.0f; shapeDef.friction = 0.3f; body->CreateShape(&shapeDef); body->SetMassFromShapes(); 这就是初始化过程。现在我们已经准备好开始模拟了。 2.4 模拟(Box2D 的)世界 我们已经初始化好了地面盒和一个动态盒。现在是让牛顿接手的时刻了。我们只有少数几个问题需要 考虑。 Box2D 中有一些数学代码构成的积分器(integrator),积分器在离散的时间点上模拟物理方程,它将 与游戏动画循环一同运行。所以我们需要为 Box2D 选取一个时间步,通常来说游戏物理引擎需要至少 60Hz 的速度,也就是 1/60 的时间步。你可以使用更大的时间步,但是你必须更加小心地为你的世界调 整定义。我们也不喜欢时间步变化得太大,所以不要把时间步关联到帧频(除非你真的必须这样做)。直截 了当地,这个就是时间步: float32 timeStep = 1.0f / 60.0f; 除了积分器之外,Box2D 中还有约束求解器(constraint solver)。约束求解器用于解决模拟中的所有 约束,一次一个。单个的约束会被完美的求解,然而当我们求解一个约束的时候,我们就会稍微耽误另 一个。要得到良好的解,我们需要迭代所有约束多次。建议的 Box2D 迭代次数是 10 次。你可以按自己 的喜好去调整这个数,但要记得它是速度与质量之间的平衡。更少的迭代会增加性能并降低精度,同样 地,更多的迭代会减少性能但提高模拟质量。这是我们选择的迭代次数:
int32 iterations = 10; 注意时间步和迭代数是完全无关的。一个迭代并不是一个子步。一次迭代就是在时间步之中的单次遍 历所有约束,你可以在单个时间步内多次遍历约束。 现在我们可以开始模拟循环了,在游戏中模拟循环应该并入游戏循环。每次循环你都应该调用 b2World::Step,通常调用一次就够了,这取决于帧频以及物理时间步。 这个 Hello World 程序设计得非常简单,所以它没有图形输出。胜于完全没有输出,代码会打印出动 态物体的位置以及旋转角度。Yay!这就是模拟 1 秒钟内 60 个时间步的循环: for (int32 i = 0; i < 60; ++i) { world.Step(timeStep, iterations); b2Vec2 position = body->GetPosition(); float32 angle = body->GetAngle(); printf("%4.2f %4.2f %4.2f\n", position.x, position.y, angle); } 2.5 清理工作 当一个世界对象超出它的作用域,或通过指针将其 delete 时,所有物体和关节的内存都会被释放。 这能使你的生活变得更简单。然而,你应该将物体,形状或关节的指针都清零,因为它们已经无效了。 2.6 关于 Testbed 一旦你征服了 HelloWorld 例子,你应该开始看 Box2D 的 testbed 了。testbed 是一个单元测试框 架以及演示环境,这是一些它的特点: • 可移动和缩放的摄像机 • 鼠标拣选动态物体的形状 • 可扩展的测试集 • 通过图形界面选择测试,调整参数,以及设置调试绘图 • 暂停和单步模拟 • 文字渲染
在 testbed 中有许多 Box2D 的测试用例,以及框架本身的实例。我鼓励你通过研究和修改它来学习 Box2D。 注意:testbed 是使用 freeglut 和 GLUI 写成的,testbed 本身并不是 Box2D 库的一部分。Box2D 本身对于渲染是无知的,就像 HelloWorld 例子一样,使用 Box2D 并不一定需要渲染。 3. API 设计 3.1 内存管理 Box2D 的许多设计决策都是为了能快速有效地使用内存。在本节我将论述 Box2D 如何和为什么要分 配内存。 Box2D 倾向于分配大量的小对象(50-300 字节左右)。这样通过 malloc 或 new 在系统的堆(heap)上 分配内存就太低效,并且容易产生内存碎片。多数这些小型对象的生命期都很短暂,例如触点 (contact),可能会维持几个时间步。所以我们需要为这些对象提供一个有效的分配器(allocator)。 Box2D 的解决方案是使用小型对象分配器(SOA),SOA 维护了许多不定尺寸的可生长的池(growable pool)。当有内存分配请求时,SOA 会返回一块最匹配的内存。当内存块释放掉以后,它会回到池中。 这些操作都十分快速,导致很小的堆流量。
因为 Box2D 使用了 SOA,所以你应该永远也不必去 new 或 malloc 物体,形状或关节。你只需分配 一个 b2World,它为你提供了创建物体,形状和关节的工厂(factory)。这使得 Box2D 可以使用 SOA 并 且将赤裸的细节隐藏起来。永远也不要去 delete 或 free 一个物体,形状或关节。 当执行一个时间步的时候,Box2D 会需要一些临时的内存。为此,它使用了一个栈(stack)分配器来消 除单步堆分配。你不需要关心栈分配器,但在此作一个了解还是不错的。 3.2 工厂和定义 如上所述,内存管理在 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) 当你创建一个物体或关节的时候,你需要提供一个定义(definition,简写为 def)。这些定义包含了创 建物体或关节的所有相关信息。通过这样的方法,我们就能预防构造错误,使函数参数的数量较少,提 供有意义的默认参数,并减少访问子(accessor)的数量。 因为形状必须有父物体,所以 b2Body 上有创建和摧毁形状的工厂: b2Shape* b2Body::CreateShape(const b2ShapeDef* def) void b2Body::DestroyShape(b2Shape* shape) 工厂并不保留到定义的引用,所以你可以在栈上创建定义,临时的保存它们。 3.3 单位 Box2D 使用浮点数,所以必须使用一些公差来保证它正常工作。这些公差已经被调谐得适合米-千克- 秒(MKS)单位。尤其是,Box2D 被调谐得能良好地处理 0.1 到 10 米之间的移动物体。这意味着从罐头 盒到公共汽车大小的对象都能良好地工作。 作为一个 2D 物理引擎,如果能使用像素作为单位是很诱人的。很不幸,那将导致不良模拟,也可能 会造成古怪的行为。一个 200 像素长的物体在 Box2D 看来就有 45 层建筑那么大。想象一下使用一个被 调谐好模拟玩偶和木桶的引擎去模拟高楼大厦的运动。那并不有趣。 • 注意:Box2D 已被调谐至 MKS 单位。移动物体的尺寸大约应该保持在 0.1 到 10 米之间。你可能 需要一些缩放系统来渲染你的场景和物体。Box2D 中的例子是使用 OpenGL 的视口来变换的。
3.4 用户数据 b2Shape,b2Body 和 b2Joint 类都允许你通过一个 void 指针来附加用户数据。这在你测试 Box2D 数据结构,以及你想把它们联系到自己的引擎中的时候是较方便的。 举个典型的例子,在角色上的刚体中附加到角色的指针,这就构成了一个循环引用。如果你有角色, 你就能得到刚体。如果你有刚体,你就能得到角色。 GameActor* actor = GameCreateActor(); b2BodyDef bodyDef; bodyDef.userData = actor; actor->body = box2Dworld->CreateBody(&bodyDef); 这是一些需要用户数据的案例: • 使用碰撞结果给角色施加伤害 • 当玩家进入一个包围盒时播放一段脚本事件 • 当 Box2D 通知你一个关节即将摧毁时访问一个游戏结构 记得用户数据是可选的,并且能放入任何东西。然而,你需要保持一致性。例如,如果你想在一个物 体中保存一个角色的指针,那你就应该在所有物体中都保存一个角色指针。不要在一个物体中保存角色 指针,却在另一个物体中保存一个其它指针。这可能会导致程序崩溃。 3.5 C++ 相关面 C++ 有着强大的封装和多态,但在 API 设计方面却不那么强大。在创建一个 C++ 库的时候总会存在 许多有意义的取舍。 我们是否应该使用抽象工厂或 pimpl 模式?它们能使 API 看起来更简洁,但它们最终会妨碍调试和高 效开发。 我们是否有必要使用私有数据和友元(friend)?也许,但最后友元的数量可能会变得荒谬。 我们是否应该用一个 C-API 封装 C++ 代码?也许,但这是额外的工作,并且可能会导致非最佳的内 部选择。另外,C-API 也难于调试和维护,一个 C-API 同时也破坏了封装。 我为 Box2D 选择了最容易的方法。有时候一个类可以包含其设计和函数,所以我使用公有函数和私 有数据。其它情况下我使用了全部公有的成员的类和结构。这样的选择使我能快速地开发代码,很容易 调试,并且当维护紧密的封装时最小化了内部混乱。如此,你并不能看见一个简单干净的 API。当然, 你拥有的这个漂亮的手册能帮助你摆脱困扰 :) 3.6 稻草人 如果你不喜欢这个 API 的设计,that's ok!你拥有源代码!诚挚地,如果你有任何关于 Box2D 的反 馈,请在 论坛 里留下意见。
分享到:
收藏