你好,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,将实体定义传递给world对象以创建地面实体。世界对象不保留对实体定义的引用。默认情况下,实体是静态的。静止物体不会与其他静止物体碰撞,并且是不可移动的。

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不保留对形状的引用。它将数据克隆到一个新的B2形状对象

请注意,每个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。

接下来,我们使用设备定义创建并附加一个多边形形状。首先,我们创建一个长方体形状:

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

接下来,我们使用长方体创建一个fixture定义。请注意,我们将“密度”(density)设置为1。默认的“密度”(density)为零。此外,形状上的摩擦力设置为0.3。

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

使用fixture定义,我们现在可以创建fixture。这会自动更新实体的质量。可以向实体添加任意数量的固定装置。每一个都占总质量的一部分。

body->CreateFixture(&fixtureDef);

这就是初始化。我们现在可以开始模拟了。

模拟世界

所以我们已经初始化了地盒和动态盒。我们现在准备好做牛顿的事了。我们还有几个问题要考虑。

Box2D使用一种称为积分器的计算算法。积分器在离散时间点模拟物理方程。这与传统的游戏循环相结合,我们基本上在屏幕上有一本关于运动的翻页书。所以我们需要为Box2D选择一个时间点。一般来说,物理引擎的游戏,像一个时间步至少快到60赫兹或1/60秒。你可以避开更大的时间步长,但你必须更加小心地为你的世界设置定义。我们也不喜欢时间的变化太大。可变时间步长会产生变量结果,这使得调试变得困难。所以不要把时间步长和帧速率联系在一起(除非你真的,真的必须这么做)。不用多费吹灰之力,这里是时间步。

float timeStep = 1.0f / 60.0f;

除了积分器之外,Box2D还使用了一个称为约束求解器的较大代码位。约束解算器一次解决一个模拟中的所有约束。单个约束可以完美地解决。然而,当我们解决一个约束时,我们会稍微破坏其他约束。为了得到一个好的解决方案,我们需要迭代所有约束多次。

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

Box2D的建议迭代次数是速度8次,位置3次。你可以根据自己的喜好调整这个数字,但要记住这在性能和准确性之间有一个折衷。使用较少的迭代可以提高性能,但精度会受到影响。同样,使用更多的迭代会降低性能,但会提高模拟的质量。对于这个简单的例子,我们不需要太多的迭代。下面是我们选择的迭代计数。

int32 velocityIterations = 6;
int32 positionIterations = 2;

请注意,时间步长和迭代计数是完全不相关的。迭代不是子步骤。一个解算器迭代是在一个时间步内对所有约束进行一次迭代。可以在一个时间步内对约束进行多次传递。

我们现在可以开始模拟循环了。在游戏中,模拟循环可以与游戏循环合并。在每次通过你的游戏循环你调用b2World::步骤. 只要一个电话就足够了,这取决于你的帧速率和你的物理时间步长。

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删除时,为实体、装置和关节保留的所有内存都将被释放。让你的生活变得更轻松。但是,您将需要使您拥有的任何主体、固定装置或关节指针无效,因为它们将变得无效。

results matching ""

    No results matching ""