内容很多摘自
Aman JIANG(江超宇)翻译的Box2D v2.0.1 用户手册
Box2D的发布包中有个Hello World程序。程序创建了一个大大的地面盒(ground box)和一个小小的动态盒(dynamic box)。盒子的位置随着时间的变化而变化。代码没有涉及到图形界面,你只能在控制台中看到文字输出
这是个很好的例子, 展示了怎么使用Box2D。
每个Box2D程序开始时都会创建一个b2World对象。b2World是物理枢纽(physics hub), 用于管理内存、对象和模拟。根据自己的实际情况, 你可以在栈, 堆或数据区中创建出world。
创建Box2D的world很简单。首先, 我们要定义重力矢量,另外还要告诉world是否允许body在静止时休眠。休眠中的body不需要任何模拟。
b2Vec2 gravity(0.0f, -10.0f);
bool doSleep = true;
现在可以创建world对象了。注意,在这里我们是在栈中创建world, 所以world不能离开它的作用域。
b2World world(gravity, doSleep);
我们已经有了自己的物理世界, 开始向里面加东西了。
body用以下步骤来创建:
1. 用位置(position), 阻尼(damping)等来定义body
2. 通过world对象来创建body
3. 用形状(shape), 摩擦(friction), 密度(density)等来定义fixture
4. 在body上来创建fixture
第一步,创建ground body。我们需要一个body定义。在定义中,我们指定ground body的初始位置。
b2BodyDef groundBodyDef;
groundBodyDef.position.Set(0.0f,-10.0f);
第二步, 将body定义传給world对象, 创建ground body。world对象并不保留body定义的引用。ground body是作为静态物体(static body)创建的。静态物体和其它静态物体之间并没有碰撞, 它们是固定的。当body的质量为零时, Box2D就认为它是静态的。物体质量的默认值就为零, 所以它们默认就是静态的。
b2Body* groundBody =world.CreateBody(&groundBodyDef);
第三步, 创建地面多边形。我们用简便函数SetAsBox使得地面多边形构成一个盒子形状,盒子的中心点就是父body的原点。
b2PolygonShape groundBox;
groundBox.SetAsBox(50.0f, 10.0f);
SetAsBox函数接收半个宽度和半个高度作为参数。因此在这种情况下,地面盒就是100个单位宽(x轴),20个单位高(y轴)。Box2D已被调谐到使用米,千克和秒做单位。你可以认为长度单位就是米。当物体的大小跟真实世界一样时,Box2D通常工作良好。例如,一个桶约1米高。由于浮点算法的局限性,使用Box2D模拟冰川或沙尘的运动并不是一个好主意。
第四步, 我们创建shape fixture, 以完成ground body。这步中,我们有个简便方法。我们并不需要修改fixture默认的材质属性, 可以直接将形状传给body而不需要创建fixture的定义。随后的教程中, 我们将会看到如何使用fixture定义来定制材质属性。
groundBody->CreateFixture(&groundBox);
Box2D并不保存shape的引用。它把数据复制到一个新的b2Shape对象中。
注意,每个fixture都必须有一个父body,即使fixture是静态的。然而,你可以把所有静态fixture都依附于单个静态body之上。之所以需要这个静态body, 是为了保证Box2D内部的代码更具一致性,以减少潜在的bug数量。
可能你已经注意到, 多数Box2D类型都有b2前缀。这是为了降低它和你的代码之间名字冲突的机会。
现在我们已经有了一个地面body,我们可以使用同样的方法来创建一个动态body。除尺寸之外的主要区别是, 我们必须为动态body设置质量属性。
首先我们用CreateBody创建body。默认情况下,body是静态的, 所以在构造时候应该设置b2BodyType使得body成为动态
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.position.Set(0.0f, 4.0f);
b2Body* body =world.CreateBody(&bodyDef);
注意
如果你想body受力的影响而运动, 你必须将body的类型设为b2_dynamicBody。
然后,我们创建一个多边形shapde, 并将它附加到fixture定义上。我们先创建一个box shape:
b2PolygonShape dynamicBox;
dynamicBox.SetAsBox(1.0f, 1.0f);
接下来我们使用box创建一个fixture定义。注意, 我们把密度值设置为1,而密度值默认是0。并且,fixture的摩擦系数设置为0.3。
b2FixtureDef fixtureDef;
fixtureDef.shape = &dynamicBox;
fixtureDef.density = 1.0f;
fixtureDef.friction = 0.3f;
使用fixture定义, 我们现在就可以创建fixture。这会自动更新body的质量。要是你喜欢, 你可以为body添加许多不同的fixture。每个fixture都会增加物体的总质量。
body->CreateFixture(&fixtureDef);
这就是初始化过程。现在我们已经做好准备,可以开始模拟了。
我们已经初始化好了地面box和一个动态box。该让牛顿来接手了。我们只有少数几个问题需要考虑。
Box2D使用了一个叫积分器(integrator)的数值算法。 积分器在离散的时间点上模拟连续的物理方程。 它与传统的游戏动画循环一同运行。我们需要为Box2D选取一个时间步。通常来说用于游戏的物理引擎需要至少 60Hz 的速度,也就是 1/60 的时间步。你可以使用更大的时间步,但是你必须更加小心地为你的世界调整定义。我们也不喜欢时间步变化得太大,所以不要把时间步关联到帧频(除非你真的必须这样做)。直截了当地,这个就是时间步:
float32 timeStep = 1.0f / 60.0f;
除积分器外,Box2D代码还使用了约束求解器(constraint solver)。约束求解器用于解决模拟中的所有约束,一次一个。单个的约束会被完美的求解,然而当我们求解一个约束的时候,我们就会稍微耽误另一个。要得到良好的解,我们需要多次迭代所有约束。
约束求解有两个阶段:速度、位置。在速度阶段,求解器会计算必要的冲量,使得物体正确运动。而在位置阶段,求解器会调整物体的位置,减少物体之间的重叠。每个阶段都有自己的迭代计数。此外,如果误差已足够小的话,位置阶段的迭代可能会提前退出。
对于速度和位置,建议的Box2D迭代次数都是10次。你可以按自己的喜好去调整这个数字,但要记得它是性能与精度之间的折中。更少的迭代会增加性能但降低精度,同样地,更多的迭代会降低性能但能提高模拟质量。对于这个简单示例,我们不需要多次迭代。这是我们选择的迭代次数。
int32 velocityIterations = 6;
int32 positionIterations = 2;
时间步和迭代数是完全无关的。一个迭代并不是一个子步。一次迭代就是在时间步之中的单次遍历所有约束,你可以在单个时间步内多次遍历约束。
现在我们可以开始模拟循环了, 在你的游戏中, 模拟循环和游戏循环可以合并起来。每次游戏循环你都应该调用b2World::Step, 通常调用一次就够了, 这取决于帧频以及物理时间步。步进后,你应当调用b2World::ClearForces清除你施加到body上的任何力。
这个Hello World程序设计得非常简单, 它没有图形输出。代码会打印出动态body的位置以及旋转角, 有文字输出总比完全没有输出好。这就是模拟 1 秒钟内 60 个时间步的循环:
for (int32 i = 0; i < 60; ++i)
{
world.Step(timeStep,velocityIterations, positionIterations);
world.ClearForces();
b2Vec2 position =body->GetPosition();
float32 angle =body->GetAngle();
printf("%4.2f %4.2f%4.2f\n", position.x, position.y, angle);
}
输出展示了动态box降落到地面的情况。你的输出看起来应当是这样:
0.00 4.00 0.00
0.00 3.99 0.00
0.00 3.98 0.00
...
0.00 1.25 0.00
0.00 1.13 0.00
0.00 1.01 0.00
当world对象超出它的作用域,或通过指针将其 delete 时, 分配给body, fixture, joint使用的内存都会被释放。这能使你的生活变得更简单。然而,你应该将body, fixture或joint的指针都清零,因为它们已经无效了。
一旦你征服了 HelloWorld 例子,你应该开始看 Box2D 的 testbed 了。testbed 是个单元测试框架,也是个演示环境, 这是它的一些特点:
• 可移动和缩放的摄像机
• 可用鼠标选中依附在动态物体上的形状
• 可扩展的测试集
• 通过图形界面选择测试,调整参数,以及设置调试绘图
• 暂停和单步模拟
• 文字渲染
在 testbed 中有许多 Box2D 的测试用例,以及框架本身的实例。我鼓励你通过研究和修改它来学习 Box2D。
注意:testbed 是使用 freeglut 和 GLUI 写成的,testbed 本身并不是 Box2D 库的一部分。Box2D本身不知道如何渲染,就像 HelloWorld 例子一样,使用 Box2D 并不一定需要渲染。