Oh!Coder

Coding Life

Box2D C++ 教程-物体

| Comments

声明:本教程翻译自:Box2D C++ tutorials-Bodies,仅供学习参考。

  • 物体(Bodies)

物体是物理场景中的基本对象,但是这里的物体并不是你看到的实际互相弹跳碰撞的实物。听起来很费解吗?挺住!马上做解释!

你可以把物体想象成是一种看不见摸不着的实物的属性。这些不可见的属性是:

-质量(mass)-实物到底有多重
-速度(velocity)-某方向上实物到底运动多快
-转动惯量(rotational inertia)-开始或停止转动需要多大的力
-角速度(angular velocity)-某方向实物转动的速度有多快
-位置(location)-实物在哪
-角度(angle)-实物面向哪个方向

即便你知道一个对象的所有这些特性,你仍然不清楚这个对象的长像或者它与其它物体产生碰撞的时候会作出什么样的反应。我们需要使用定制器(fixtures)来定义物体的形状和大小,关于定制器我们会在本教程的下一个话题进行讲解。目前我们还是使用一个简单的盒子来代替随后出现的定制器(fixture)的及其细节。让我们创建一个物体并为它设置一些基本属性,来看看是如何工作的吧。

有三种类型的物体:静态物体(static),动态物体(dynamic)以及运动学物体(kinematic)。前两个顾名思义看起来更好理解一些。最后一个看起来理解起来并不是那么的直观。目前先忽略一些细节,随后再进行解释。首先我们将创建一个动态物体,我们可以在场景中的地面上让其进行移动,然后试着设置速度,等等。让我们开始吧。

  • 创建物体

物体的创建由一些定义组成,通过这些定义来创建物体自身。如果你想创建多个相同或相似的物体,这种方式比较方便(译者注:这里所说的创建物体的定义就是下面代码中的b2BodyDef结构体的定义,以后所创建的物体都可以根据这个结构体做快速初始化)。在Footest类的构造方法里,添加如下代码,来创建物体定义:

1
2
3
4
b2BodyDef myBodyDef;
myBodyDef.type = b2_dynamicBody; //this will be a dynamic body
myBodyDef.position.Set(0, 20); //set the starting position
myBodyDef.angle = 0; //set the starting angle

这对创建一个基本的物体就足够了。记住这里的物体没有大小、形状,这里不会对这些内容进行定义。你可能会想为什么这里还没有质量-通常为物体设置质量的方式是通过为物体添加定制器(fixtrues)来完成的,下面会进行到这一步骤。现在,使用这个定义创建一个活生生的实例对象吧:

1
b2Body* dynamicBody = m_world->CreateBody(&myBodyDef);

这里,我们用到了父类Test中类型为b2World类型的成员变量m_world。world类型的对象就像是Box2D中的老大,它掌管着创建和销毁物理对象。稍后的教程里我们会看到关于world类型更多的细节。好的,现在我们有了一个物体,但是就像本话题开始时提到那样,基本上物体是不可见的,如果现在你运行程序,什么都不会看到(如果你选中右侧控制面板中“质心(Center of Masses)”选项,最多你可以看到物体下落的位置)。

为物体设置大小,形状以及其它更明确的性质,通过为其添加定制器(fixtures)来实现。此外,即便为物体添加默认的定制器也会影响物体的质量。物体可以附加多个定制器,附加的每一个定制器都会影响物体的总质量。目前,我们只为物体添加一个简单的定制器,一个四方形,定制器本来是下一次教程的话题,现在细说有点早。

1
2
3
4
5
6
7
b2PolygonShape boxShape;
boxShape.SetAsBox(1,1);

b2FixtureDef boxFixtureDef;
boxFixtureDef.shape = &boxShape;
boxFixtureDef.density = 1;
dynamicBody->CreateFixture(&boxFixtureDef);

虽然上面这段大部分的代码现在你都可以忽略,但是注意一下密度设置这行代码。定制器的质量是通过其自身面积(area)和密度进行相乘来计算的,最终算为物体的质量。

现在,当你运行程序,会看到一个下落的小盒子。如果你足够快,你可以用鼠标捕捉它,然后随意拖拽,如果小盒子丢出屏幕范围可以按restart按钮(R键)重新开始。既然这是个动态物体,那么它可以移动并且旋转,并受到重力的影响。

pic

  • 设置物体属性

现在,我们设置一些文章开始提到的属性,看看会发生什么。比如说,改变初始位置和角度:

1
dynamicBody->SetTransform( b2Vec2( 10, 20 ), 1 );

这将会使物体的初始位置向右移动10个单位,向上移动20个单位,并且逆时针旋转1弧度。Box2D使用弧度作为角度值的单位,如果你像我一样想使用角度值,那么需要多做一些工作,或许会像下面这样:

1
2
3
#define DEGTORAD 0.0174532925199432957f
#define RADTODEG 57.295779513082320876f
dynamicBody->SetTransform(b2Vec2(10, 20), 45 * DEGTORAD ); //45 degrees counter-clockwise

我们也可以设置物体的线速度和角速度:

1
2
dynamicBody->SetLinearVelocity(b2Vec2( -5, 5 ));//moving up and left 5 units per second
dynamicBody->SetAngularVelocity(-90 * DEGTORAD);//90 degrees per second clockwise
  • 静态物体(Static bodies)

现在让我们看看什么是静态物体。既然我们对物体已经有了一些定义和一个定制器,那我们就重用它们,只修改一些必须的特性:

1
2
3
4
myBodyDef.type = b2_staticBody; //this will be a static body
myBodyDef.position.Set(0, 10); //slightly lower position
b2Body* staticBody = m_world->CreateBody(&myBodyDef); //add body to world
staticBody->CreateFixture(&boxFixtureDef); //add fixture to body

注意这里我们根本不需要改变正方形定制器。运行程序,这时你可以在场景中看到另外一个盒子,但是这次它将不会移动。而且你还会发现即便你像上面那样成功使用setTransform方法改变静态物体的位置,设置静态物体的速度属性,也不会有任何效果。

pic

  • 运动学物体(Kinematic bodies)

最后,让我们来看看有关于运动学物体的部分。正如我们之前看到的动态物体可以移动,但静态物体不能移动。当一个静态物体和动态物体发生碰撞的时候,静态物体总能“获胜”并坚挺在地面上,动态物体根据实际情况被弹回,两个物体之间不会发生重叠。运动学物体非常像静态物体,当它与动态物体相撞之后,总能保持自己不动,把动态物体反弹回去,与静态物体唯一不同的是,运动学物体可以被移动。

可以像下面这样创建一个运动学物体:

1
2
3
4
5
6
7
myBodyDef.type = b2_kinematicBody;//this will be a kinematic body
myBodyDef.position.Set(-18, 11); //start from left side,slightly above the static body
b2Body* kinematicBody = m_world->CreateBody(&myBodyDef); //add body to world
kinematicBody->CreateFixture(&boxFixtureDef); //add fixture to body

kinematicBody->SetLinearVelocity( b2Vec2( 1, 0 ) );//move right 1 unit per second
kinematicBody->SetAngularVelocity( 360 * DEGTORAD );//1 turn per second counter-clockwise

pic

新创建的物体在场景中可以被移动或者旋转,但不会被重力所影响,并且当与动态物体发生碰撞的时候也不会受到影响。注意当它与静态物体发生接触的时候,它们之间是没有相互作用的。

在大多数游戏中,动态物体通常被用来创建玩家和其它场景中的角色,静态物体被用来创建墙,地板等等。运动学物体通常用来扮演那些可以移动或旋转但是不能被动态物体所撞动的物体。一个比较恰当的例子是横屏游戏中一个移动的平台,该平台可以使用运动学物体来模拟-这可以保证不论玩家如何跳跃,都不能与之由于相撞而产生位移。

  • 获取物体属性(Getting body properties)

很多情况下你都想知道物体目前在哪儿或者移动有多快,旋转角度等等。实现这个很简单,让我们现在就做一个尝试。为此,我们需要在Step()方法中访问物体变量,所以我们需要声明一个类的成员变量来代替只在构造函数中声明的变量。

1
2
//in the class itself, not inside the constructor!
b2Body* dynamicBody;

..然后使用类的成员变量替换物体类型的局部变量。现在在Step()方法中,我们可以加入如下代码来打印出物体的一些相关信息:

1
2
3
4
5
6
7
8
b2Vec2 pos = dynamicBody->GetPosition();
float angle = dynamicBody->GetAngle();
b2Vec2 vel = dynamicBody->GetLinearVelocity();
float angularVel = dynamicBody->GetAngularVelocity();
m_debugDraw.DrawString(5, m_textLine,"Position:%.3f,%.3f Angle:%.3f", pos.x, pos.y, angle * RADTODEG);
m_textLine += 15;
m_debugDraw.DrawString(5, m_textLine, "Velocity:%.3f,%.3f Angular velocity:%.3f", vel.x, vel.y, angularVel * RADTODEG);
m_textLine += 15;

pic

你可以通过GetPositon()和GetAngle()方法返回SetTransform所设置的值,而且不会发生值的改变。比如说,像下面这行代码根本不会引起物体位置的移动。

1
body->SetTransform( body->GetPosition(), body->GetAngle() );

当然了,这么做并没有什么用处,但是如果你只想改变位置或者角度,你就可以这么做。

  • 遍历世界中的物体

如果你想监视世界中的所有物体,你可以像下面这样做。GetBodyList()方法可以返回物体链表的第一个元素。

1
2
3
4
for ( b2Body* b = m_world->GetBodyList(); b; b = b->GetNext())
{
    //do something with the body 'b'
}
  • 清除

当一个物体完成了它的使命,你可以通过调用世界对象的DestroyBody方法将其移除:

1
m_world->DestroyBody(dynamicBody);

当像上面这样销毁物体之后,物体所附加的所有定制器(fixtures)和连接器(joins)都会被销毁。记住!当使用这个方法之后不要再调用这个已经删除的物体指针!

Comments