时间:2021-07-01 10:21:17 帮助过:53人阅读
内容很多摘自 Aman JIANG(江超宇)翻译的 Box2D v2.0.1 用户手册 第02章 Hello Box2D Box2D的发布包中有个Hello World程序。程序创建了一个大大的地面盒(ground box)和一个小小的动态盒(dynamic box)。盒子的位置随着时间的变化而变化。代码没有涉及到图形界
内容很多摘自
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 并不一定需要渲染。