欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Hello Box2D

程序员文章站 2024-03-16 16:44:04
...

你好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或关节指针,因为它们将变为无效。

相关标签: Box2d

上一篇: leetcode——767.重构字符串

下一篇: