第二章 HelloBox2D

在Box2D中包含一个HelloWorld项目。程序创建了一个大的地面盒子(ground box)和一个小的动态盒子。工程代码中没有包含任何图形,你仅能在文字输出控制台中看到随时间更新的盒子位置。

从如何搭建并运行一个简单地Box2D模拟程序的角度来讲,这是一个很好的例子。

 

2.1创建一个世界

每个Box2D程序都从创建一个b2World对象开始,b2World是一个管理内存、物体和模拟的中心,你可以在堆栈中或者数据区域中为物理世界对象分配空间。

创建一个Box2D世界对象很容易,首先我们定义一个重力向量:

b2Vec2gravity(0.0f, -10.0f)

接着我们创建世界对象。注意我们这里是在栈中创建世界对象,因此我们需要在程序中保持该对象。

b2Worldworld(gravity);

这样我们就创建好了我们的物理世界,我们继续向其中添加一些其他的东西。

 

2.2创建一个地面盒子

物体通过下面的步骤创建完成:

1.    创建一个具有位置和阻尼(damping)(position)等属性的物体定义(BodyDef)

2.    利用世界对象创建物体

3.    使用形状、摩擦力、密度等属性定义装置定义(FixtureDef)

4.    在物体上创建装置

第一步我们创建地面盒子,需要一个物体定义来指定盒子的位置。

b2BodyDefgroundBodyDef;

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

第二步我们将物体定义作为参数传递给世界对象来创建地面盒子。世界对象并不保存物体定义的引用。默认创建出来的物体都是静态的(static),静态物体不能移动,也不会与其他静态物体发生碰撞。

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

第三步我们创建一个地面多边形作为地面盒子的形状,我们使用SetAsBox方法来创建一个矩形,将盒子居中对齐到父物体的原点。

b2PolygonShapegroundBox;

groundBox.SetAsBox(50.0f,10.0f);

SetAsBox方法参数中的长宽属性只是实际长宽的一半,所以在本例中地面盒子的宽实际为100(x轴),长为20(y轴)。Box2D中的基本单位为米,千克和秒,所以我们可以认为这个例子中多边形的尺寸是以米作单位的。Box2D在处理大小和真实世界的物体相近的东西时,模拟效果最好,例如,在Box2D中一个桶应该大约1米高。相反的,由于浮点数计算的限制,用Box2D来模拟冰川或者模拟灰尘的粒子效果可不是一个好主意。

我们接着做完第四步,创建装置,来完成地面物体的创建。对于这一步,我们有一个小的捷径,由于我们不需要去修改默认的装置属性,所以我们直接将形状作为第一个参数传给物体对象,而不需要去创建一个装置定义(稍后我们会看到如何通过装置定义来定义物体的基本属性),传入的第二个参数是密度,单位是千克/平方米(这个地方应该因为是二维的物体,所以单位是平方米而不是立方米),静态物体默认没有质量,所以密度在这里没有作用。

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

Box2D不保留形状的引用,而是将形状对象的一个拷贝作为参数传入。

注意每一个装置都有一个父物体,即使装置是静态的。但是,你可以将所有的静态装置附加(attach)到一个相同的静态物体上。

当你将一个形状通过装置附加到物体上时,形状的坐标系就变为物体的本地坐标系了,所以当物体移动时,形状也随之移动。装置的世界变换继承自它的父物体,没有任何装置能够脱离物体进行变换,所以我们不去在物体上调整形状的位置,另一方面,移动和修改物体上的形状也是不支持的,理由很简单:一个形状可变的物体不是刚体,而Box2D是一款基于刚体的物理引擎,Box2D中的很多假设都是基于刚体模型的,所以如果物体不是刚体,很多事情就不对了。

 

2.3创建一个动态物体

现在我们有了一个地面物体,我们通过同样的方法来创建一个动态物体,唯一的不同时(除了尺寸之外),我们需要指定动态物体的质量属性。

首先我们通过CreateBody来创建物体,默认创建出来的是静态物体,所以在构造时我们需要将b2BodyType属性设置为动态的。

b2BodyDefbodyDef;

bodyDef.type= b2_dynamicBody;

bodyDef.position.Set(0.0f,4.0f);

b2Body*body = world.CreateBody(&bodyDef);

接着我们用装置定义来创建并绑定一个形状。首先我们创建一个盒子形状:

b2PolygonShapedynamicBox;

dynamicBox.SetAsBox(1.0f,1.0f);

接着我们用这个创建好的形状来创建一个装置定义,注意我们设置定义的密度属性为1,默认的密度属性为0,此外我们将形状的摩擦力属性设置为0.3。

b2FixureDeffixtureDef;

fixtureDef.shape= &dynamicBox;

fixtureDef.density= 1.0f;

fixtureDef.friction= 0.3f;

现在我们可以使用装置定义来定义一个物体了,物体的质量属性会被自动更新,你可以为物体添加多个装置,每个装置添加完成后,物体的质量都会随之增加。

body-CreateFixture(&fixtureDef);

这样我们就完成了初始化,我们下面开始进行模拟。

 

2.4模拟世界(Box2D)

我们完成了地面盒子和动态盒子的初始化操作,下面该让牛顿出场了。在此之前,我们还需要考虑一些事情。

Box2D使用一套名为集成器(integrator)的算法在离散的时间点上来模拟物理方程,这和我们传统游戏中使用的循环方法相同(例如我们通过循环帧来在屏幕上表现一本翻动的书)。因此我们需要为Box2D设置一个时间间隔(time step),大多数游戏引擎都希望时间间隔至少为1/60秒(频率为60Hz)。你也可以选择更大的时间间隔,但是你要在初始化世界的时候更加小心。我们不希望对时间间隔做过多的修改,变化的时间间隔导致变化的结果,调试的难度也随之增加,因此不在万不得已的情况下,不要将时间间隔和你的帧频率混为一谈。话不多说,下面我们定义好时间间隔。

float32timeStep = 1.0f/60.0f;

除了集成器外,Box2D还用一大段代码定义了约束解析器(constraint solver),用来在模拟的过程中逐个解析所有的约束。一个单独的约束能够被完美的解析,然而,当我们解析某个约束时,其他约束会被暂时中断,因此,如果需要得到一个好的解析结果,我们需要多次遍历所有的约束。

约束解析器的工作流程分为两个阶段:一个速度阶段,一个位置阶段。在速度阶段解析器计算所有能够使物体正确运动的冲量,在位置阶段,解析器调整物体的位置,以避免重叠,防止关节脱落。每个阶段有自己的迭代次数,在仅需微调的情况下位置阶段有可能提前结束。

Box2D中建议设置速度阶段的迭代次数为8次,位置阶段为3次,你可以自己按照需要做修改,但是请在计算的精确度和程序的运行效率上做好平衡。使用更少的迭代次数能够获得更高的执行效率,但是精确度要差一些,反之,更多的迭代次数带来更高的精度,但是却损失了效率。对于我们这个简单的例子来说,我们不需要太多的迭代次数,我们按照下面的代码来设定。

int32velocityIterations = 6;

int32positionIterations = 2;

注意到时间间隔和迭代次数完全无关,一次迭代并不是在一个时间间隔内,解析器做一次迭代的时间一个时间间隔,一个时间周期内可以出现多次迭代。

我们接下来可以编写用于模拟的循环了,在你的游戏中,模拟的循环代码可以被放到你游戏的循环中。在游戏的每一次循环中,调用b2World::Step,一次循环调用一次就可以了,与你的帧频率和物理时间间隔有关。

“Hello World”小程序应该尽量简洁,所以我们不加入任何的图形输出,仅用代码打印出动态盒子的位置和旋转而已。下面就是用来模拟的循环代码,模拟了60个时间间隔(总计1秒钟)。

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

 

2.5清理工作

当一个世界对象不再被保持存在或者被delete语句清除掉时,所有为了物体、装置和关节保留的内存都会被释放掉,以提高性能。然而,你需要将所有的物体、装置和关节指针指向NULL,否则这些指针将指向被释放的区域。

 

2.6测试床(Testbed)

当你完成HelloWorld这个例子后,你应该简单了解一下Box2D的测试床。测试床是一个单元测试架构和演示环境(demo environment)。下面是它的一些功能:

1.    可移动和缩放的摄影机

2.    使用鼠标选择动态物体的形状

3.    可扩展的测试集

4.    可通过界面选择测试,设置参数和调试绘图选项

5.    暂停和单步(step)模拟

6.    文字渲染

box2d 怎么引入 js项目 box2d lite_box2d 怎么引入 js项目

在测试床中有许多关于Box2D的测试用例和框架的例子。我们希望你能够通过研究学习和实际操作来更好的学习Box2D。

注意:测试床是使用freeglut和GLUI写的,测试床不是Box2D库的一部分,渲染对于Box2D来说是透明的,就像在HelloWorld例子中我们看到的一样,我们不需要任何的渲染器。