跳至正文

(转载)BOX2D V2.3.0 用户手册中文版(第2章)-Hello Box2D

(转载)BOX2D V2.3.0 用户手册中文版(第2章)-Hello Box2D

Chapter 2 Hello Box2D

Box2D的发布包中有个Hello World程序。程序创建了一个大大的地面盒(ground box)和一个小小的动态盒(dynamic box)。代码没有涉及到图形界面,你只能在控制台中看到随时间变化的盒子位置的文字输出。
这是个很好的例子, 展示了怎么学习和使用Box2D。

2.1 创建世界

每个Box2D程序开始时都会创建一个b2World对象。b2World是个物理枢纽(physics hub),用于管理内存、对象和模拟。你可以在栈, 堆或数据区中创建出world。
创建Box2D的world很简单。首先,我们定义重力矢量。

b2Vec2 gravity(0.0f, -10.0f);

现在可以创建world对象了。注意,我们是在栈中创建world, 所以world不能离开它的作用域。

b2World world(gravity);

现在我们已经有了自己的物理世界,开始向里面加东西了。

2.2 创建地面盒

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定义的引用。body默认是静态的。静态物体和其它静态物体之间并没有碰撞,它们是固定的。

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定义来定制材质属性。第二个参数是形状密度,单位是千克/平方米。静态物体的质量定义为0,因此密度对它们是没有用的。

groundBody->CreateFixture(&groundBox, 0.0f);

Box2D并不保存shape的引用。它把数据复制到一个新的b2Shape对象中。

注意
每个fixture都必须有一个父body,即使fixture是静态的。然而,你可以把所有的静态fixture都依附在单个静态body之上。

当你使用fixture 向body添加shape的时候,shape的坐标对于body来说就变成本地的了。因此当body移动的时候,shape也一起移动。fixture的世界变换继承自它的父body。fixture没有独立于body的变换。所以我们不需要移动body上的shape。不支持移动或修改body上的shape。原因很简单:形状发生改变的物体不是刚体,而Box2D只是个刚体引擎。Box2D所做的很多假设都是基于刚体模型的。如果这一条被改变的话,很多事情都会出错。

2.3 创建动态物体

现在我们已经有了一个地面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。

然后,我们创建一个多边形shape, 并将它附加到fixture定义上。我们先创建一个box shape:

b2PolygonShape dynamicBox;
dynamicBox.SetAsBox(1.0f, 1.0f);

接下来,我们使用box创建一个fixture定义。注意, 我们把密度值设置为1,而密度值默认是0。并且,shape的摩擦系数设置为0.3。

b2FixtureDef fixtureDef;
fixtureDef.shape = &dynamicBox;
fixtureDef.density = 1.0f;
fixtureDef.friction = 0.3f;

注意
一个动态body至少有一个密度不为0的fixture。否则会出现一些奇怪的行为。

使用fixture定义,我们现在就可以创建fixture。这会自动更新body的质量。要是你喜欢,你可以为body添加许多不同的fixture。每个fixture都会增加物体的总质量。

body->CreateFixture(&fixtureDef);

这就是初始化过程。现在我们已经做好准备,可以开始模拟了。

2.4 模拟(Box2D的)世界

我们已经初始化了地面box和一个动态box。该让牛顿来接手了。我们只有少数几个问题需要考虑。
Box2D使用了一种名叫积分器(integrator)的数值算法。 积分器在离散的时间点上模拟物理方程。 它与传统的游戏动画循环一同运行。我们需要为Box2D选取一个时间步(time step)。通常来说用于游戏的物理引擎需要至少 60Hz 的速度,也就是 1/60 秒的时间步。你可以使用更大的时间步,但是你必须更加小心地为你的世界调整定义。我们也不喜欢时间步变化得太大,一个变化的时间步会导致变化的结果,这会给调试带来困难。所以不要把时间步关联到帧频(除非你真的必须这样做)。直截了当地,这个就是时间步。

float32 timeStep = 1.0f / 60.0f;

除积分器外,Box2D代码还使用了约束求解器(constraint solver)。约束求解器用于解决模拟中的所有约束,一次一个。单个的约束会被完美的求解,然而当我们求解一个约束的时候,我们就会稍微干扰另一个约束。要得到良好的解,我们需要多次迭代所有约束。
约束求解有两个阶段:速度阶段和位置阶段。在速度阶段,求解器会计算必要的冲量,使得物体正确运动。而在位置阶段,求解器会调整物体的位置,减少物体之间的重叠和关节的脱节。每个阶段都有自己的迭代计数。此外,如果误差已足够小的话,位置阶段的迭代可能会提前退出。
Box2D建议的迭代次数,对于速度是8次,对于位置是3次。你可以按自己的喜好去调整这个数字,但要记得它是性能与精度之间的折中。更少的迭代会增加性能但降低精度,同样地,更多的迭代会降低性能但能提高模拟的质量。对于这个简单示例,我们不需要很多的迭代。这里是我们选择的迭代次数。

int32 velocityIterations = 6;
int32 positionIterations = 2;

注意
时间步和迭代数是完全无关的。一个迭代并不是一个子步。一次迭代就是在时间步之中的单次遍历所有约束,你可以在单个时间步内多次遍历约束。

现在我们可以开始模拟循环了,在你的游戏中,模拟循环和游戏循环可以合并起来。每次游戏循环你都应该调用b2World::Step,通常调用一次就够了,这取决于帧频以及物理时间步。
这个Hello World程序设计得非常简单,它没有图形输出。代码会打印出动态body的位置以及旋转角。这就是模拟 1 秒钟内 60 个时间步的循环。

for (int32 i = 0; i < 60; ++i)
{
    world.Step(timeStep, velocityIterations, positionIterations);
    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

2.5 清理

当world对象超出它的作用域,或通过指针将其 delete 时,分配给body、 fixture和 joint使用的内存都会被释放。这能提升性能,并使你的生活变得更简单。然而,你应该将body、fixture或joint的指针都清零,因为它们已经无效了。

2.6 The Testbed

一旦你征服了 HelloWorld 例子,你应该开始看 Box2D 的 testbed 了。testbed 是个单元测试框架,也是个演示环境,这是它的一些特点:
• 可移动和缩放的摄像机。
• 可用鼠标选中依附在动态物体上的形状。
• 可扩展的测试集。
• 通过图形界面选择测试,调整参数,以及设置调试绘图。
• 暂停和单步模拟。
• 文字渲染。

在 testbed 中有许多 Box2D 的测试用例,以及框架本身的实例。我鼓励你通过研究和修改它来学习 Box2D。

注意
testbed 是使用 freeglut 和 GLUI 写成的,testbed 本身并不是 Box2D 库的一部分。Box2D本身并不知道如何渲染。就像 HelloWorld 例子一样,使用 Box2D 并不一定需要渲染。

标签:

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注