你好Box2D

在Box2D发行版中是一个Hello World项目。 该程序将创建一个大型接地箱和一个小型动态箱。 此代码不包含任何图形。 您将看到的是文本框随时间推移在控制台中的输出。

这是如何使用Box2D并运行它的一个很好的例子。

创建一个世界

每个Box2D程序都从创建b2World对象开始。 b2World是管理内存,对象和模拟的物理中心。 您可以在堆栈,堆或数据部分上分配物理世界。

创建Box2D世界很容易。 首先,我们定义重力矢量。

b2Vec2 gravity(0.0f, -10.0f);

现在我们创建世界对象。 请注意,我们正在堆栈上创建世界,因此世界必须保持在范围内。

b2World world(gravity);

现在我们有了物理世界,让我们开始添加一些东西。

创建接地箱

使用以下步骤构建刚体:

1、用位置,阻尼等定义刚体。

2、使用世界对象创建刚体。

3、用形状,摩擦,密度等定义固定装置。

4、在刚体上创建固定装置。

对于步骤1,我们创建了地面刚体。 为此,我们需要一个刚体定义。 通过刚体定义,我们可以指定地面刚体的初始位置。

b2BodyDef groundBodyDef;
groundBodyDef.position.Set(0.0f, -10.0f);

对于步骤2,将刚体定义传递给世界对象以创建地面刚体。 世界对象不保留对刚体定义的引用。 默认情况下,刚体是静态的。 静态物体不会与其他静态物体碰撞,并且是不可移动的。

b2Body* groundBody = world.CreateBody(&groundBodyDef);

对于第3步,我们创建一个地面多边形。 我们使用SetAsBox快捷方式将地面多边形形成为盒子形状,并且盒子以父刚体的原点为中心。

b2PolygonShape groundBox;
groundBox.SetAsBox(50.0f, 10.0f);

SetAsBox函数采用一半宽度**和一半高度**(范围)。因此,在这种情况下,接地盒的宽度为100个单位(x轴),高度为20个单位(y轴)。 Box2D已调整为米,千克和秒。因此,您可以考虑以米为单位的范围。当对象是典型的现实世界对象的大小时,Box2D通常效果最好。例如,一个枪管高约1米。由于浮点算法的局限性,使用Box2D对冰川或灰尘颗粒的运动进行建模不是一个好主意。

我们在第4步中通过创建形状固定装置来完成地面物体。对于这一步,我们有一个快捷方式。我们不需要更改默认的固定装置材质属性,因此我们可以将形状直接传递给刚体,而无需创建固定装置定义。稍后,我们将看到如何将固定装置定义用于自定义材质属性。第二个参数是形状密度,单位为千克/平方米。根据定义,静态物体的质量为零,因此在这种情况下不使用密度。

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

Box2D没有保留对形状的引用。它将数据复制到一个新的b2Shape对象中。

注意,每个fixture必须有一个父刚体,即使是静态的fixture。但是,您可以将所有静态fixture附加到单个静态刚体。

当您使用一个fixture将一个形状附加到刚体时,该形状的坐标将成为刚体的局部坐标。所以当刚体移动时,形状也会移动。一个fixture的世界转换是从父刚体继承的。一个fixture没有独立于刚体的转换。所以我们不会移动刚体上的形状。移动或修改刚体上的形状是不受支持的。原因很简单:具有变形形状的物体不是刚体,但Box2D是刚体引擎。Box2D中的许多假设都是基于刚体模型。如果这一点被违反了,很多东西都会破坏。

创建动态刚体

现在我们有一个地面刚体。我们可以使用同样的技术来创建一个动态刚体。主要的区别,除了尺寸,我们还必须建立动态刚体的质量特性。

首先,我们使用CreateBody创建刚体。默认情况下,刚体是静态的,所以我们应该在构造时设置b2BodyType,使刚体是动态的。

b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.position.Set(0.0f, 4.0f);
b2Body* body = world.CreateBody(&bodyDef);

注意:你必须设置刚体类型为b2_dynamicBody,如果你想要刚体通过力去移动的话。

接下来,我们使用一个fixture定义创建并附加一个多边形形状。首先,我们创建一个盒子形状:

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

接下来,我们使用box创建一个fixture定义。注意我们把密度设为1。默认密度为零。同样,形状上的摩擦力设置为0.3。

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

注意:一个动态刚体应该至少有一个非零密度的fixture 。否则你会有奇怪的行为。

现在,我们可以使用fixture定义创建fixture。这会自动更新刚体的质量。您可以添加任意多的fixtures到一个刚体。每一个都对总质量有贡献。

body->CreateFixture(&fixtureDef);

初始化就可以了。 现在我们准备开始仿真。

模拟世界

我们已经初始化了地面盒和一个动态盒。现在我们准备让牛顿放手去做他的事。我们还有几个问题要考虑。

Box2D使用一种称为积分器的计算算法。积分器模拟离散时间点的物理方程。这与传统的游戏循环是一致的,我们在屏幕上有一个移动的翻页簿。所以我们需要为Box2D选择一个时间步长。一般来说,游戏的物理引擎,如时间步长至少要达到60Hz或1/60秒。你可以使用更大的时间步骤,但是你必须更加小心地为你的世界设置定义。我们也不喜欢时间步骤改变太多。可变的时间步长产生可变的结果,这使得调试困难。所以不要将时间步长与帧速率绑定在一起(除非你真的必须这么做)。废话不多说,下面是时间步长。

float timeStep = 1.0f / 60.0f;

除集成器外,Box2D还使用称为约束求解器的大量代码。约束求解器一次求解一次仿真中的所有约束。单个约束可以完美解决。但是,当我们解决一个约束时,我们会稍微破坏其他约束。为了获得良好的解决方案,我们需要遍历所有约束多次。

约束求解器有两个阶段:速度阶段和位置阶段。在速度阶段,求解器会计算物体正确运动所需的脉冲。在位置阶段,求解器会调整实体的位置,以减少重叠和关节分离。每个阶段都有自己的迭代计数。此外,如果误差很小,则位置阶段可能会提前退出迭代。

对于Box2D,建议的迭代计数对于速度是8,对于位置是3。您可以根据自己的喜好调整此数字,只需记住这需要在性能和准确性之间进行权衡。使用更少的迭代可以提高性能,但准确性会受到影响。同样,使用更多的迭代会降低性能,但会提高仿真质量。对于这个简单的示例,我们不需要太多的迭代。这是我们选择的迭代计数。

int32 velocityIterations = 6;
int32 positionIterations = 2;

请注意,时间步长和迭代计数是完全无关的。 迭代不是子步骤。 一个求解器迭代是在一个时间步中一次遍历所有约束。 您可以在单个时间步中对约束进行多次传递。 现在,我们准备开始模拟循环。 在您的游戏中,模拟循环可以与您的游戏循环合并。 在游戏循环的每次通过中,您都调用b2World :: Step。 通常只需调用一次就足够了,具体取决于您的帧频和物理时间步长。 Hello World程序设计得很简单,因此没有图形输出。 该代码打印出动态物体的位置和旋转。 这是一个模拟循环,可模拟60个时间步长,总计1秒钟的模拟时间。

for (int32 i = 0; i < 60; ++i)
{
    world.Step(timeStep, velocityIterations, positionIterations);
    b2Vec2 position = body->GetPosition();
    float angle = body->GetAngle();
    printf("%4.2f %4.2f %4.2f\n", position.x, position.y, angle);
}

输出显示盒子在地面盒子上下降和降落。 您的输出应如下所示:

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

清理

当世界离开范围或通过在指针上调用delete删除时,为刚体,fixture和关节保留的所有内存都将被释放。 这样做是为了提高性能并使您的生活更轻松。 但是,您将需要取消具有的任何刚体,fixture或关节指针,因为它们将变为无效。