欢迎光临Software MyZone,有问题可留言或到站点论坛发帖,争取第一时间帮忙解决 || 站点论坛:火龙论坛 || 淘宝小店:应小心的易淘屋 【欢迎大家提建设性意见】

Box2D C++ 教程 第六节:定制器(Fixtures)

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

定制器(Fixtures)

定制器用来描述场景中物体(body)的大小,形状,材质属性等。一个物体可以附加多个定制器,物体的质心会被附加定制器的顺序所影响。当两个物体相撞时,它们的定制器会决定它们的变化。定制器的主要属性如下:

  • 形状 – 多边形或圆弧
  • 恢复 – 定制器的弹力
  • 摩擦 – 光滑程度
  • 密度 – 物体大小的重量

我们会谈到上面的每一个概念,并对它们做一些实验(还有一个使定制器成为传感器的isSensor属性,之后教程里我会谈到它)。让我们就以上一次话题’Bodies’作为起点,创建一个简单的动态物体作为开始吧。那么FooTest类的构造函数看起来会是下面这个样子:

FooTest() {
  b2BodyDef myBodyDef;
  myBodyDef.type = b2_dynamicBody; //this will be a dynamic body
  myBodyDef.position.Set(-10, 20); //a little to the left

  b2Body* dynamicBody1 = m_world->CreateBody(&myBodyDef);
}

形状(Shapes)

当物体在场景中移动的时候,每一个定制器都会有一个用来进行碰撞检测的形状属性。形状可以是弧形或是多边形。让我们设置一个弧形…

b2CircleShape circleShape;
circleShape.m_p.Set(0, 0); //position, relative to body position
circleShape.m_radius = 1; //radius

…使用形状的定制器:

b2FixtureDef myFixtureDef;
myFixtureDef.shape = &circleShape; //this is a pointer to the shape above
dynamicBody1->CreateFixture(&myFixtureDef); //add a fixture to the body

现在运行程序,就像之前出现四边形的地方会有一个圆弧:

设置圆弧的坐标时,由于圆弧的位置与物体(body)的坐标位置有了关联(译者注:因为形状所附加的定制器同时被附加在物体上)。虽然我们设置圆弧本身的初始坐标为(0, 0),但是因为圆弧形状所附加的定制器同时被附加到位置在(0, 20)的物体上,所以圆弧也将被设置在坐标为(0, 20)的位置。

现在让我们尝试一下多边形。使用多边形,你可以单独的设置多边形的每一个顶点坐标来自定义形状,如果你想要盒子或者线形状,你可以使用快捷的方法进行创建。这里我们自定义了一个多边形的顶点:

b2Vec2 vertices[5];
vertices[0].Set(-1,  2);
vertices[1].Set(-1,  0);
vertices[2].Set( 0, -3);
vertices[3].Set( 1,  0);
vertices[4].Set( 1,  1);

b2PolygonShape polygonShape;
polygonShape.Set(vertices, 5); //pass array to the shape

myFixtureDef.shape = &polygonShape; //change the shape of the fixture
myBodyDef.position.Set(0, 20); //in the middle
b2Body* dynamicBody2 = m_world->CreateBody(&myBodyDef);
dynamicBody2->CreateFixture(&myFixtureDef); //add a fixture to the body

如果使用这种方式创建多边形定制器,这里有几个需要注意的事情。首先,默认情况下每个多边形的顶点为8个,如果你想要设置更多的顶点,需要修改b2Settings.h头文件中的b2_maxPolygonVertices值,顶点必须以逆时针顺序排列,并且组成的是凸多边形。凸多边形的特点是,如果沿着任意一条边做直线,多边形均在直线的同侧(要么在左侧,要么在右侧)。

如果你想要一个四边形定制器,最简单的方法就是像之前提到的使用SetAsBox方法:

polygonShape.SetAsBox(2, 1); //a 4x2 rectangle
myBodyDef.position.Set(10,20); //a bit to the right

b2Body* dynamicBody3 = m_world->CreateBody(&myBodyDef);
dynamicBody3->CreateFixture(&myFixtureDef); //add a fixture to the body

注意SetAsBox中的参数指的是盒子的“半宽”和“半高”,而且它的中心点会被附加到物体上当做物体的中心点。另外还有一点需要注意,由于定制器对形状使用的是指针引用,所以对形状所做的任何改变,我们都不需要对定制器做额外的工作,所以这次我们不用做任何其它代码的设置。

有时候相对于实体多变性而言,有可能只想要一条0厚度的直线。这可以通过另外一个便利SetAsEdge的方法来实现,此方法指出了一条直线的两点。让我们创建一个静态的线形定制器,放到场景的底部,让动态的物体落到它的上面。

myBodyDef.type = b2_staticBody; //change body type
myBodyDef.position.Set(0,0); //middle, bottom

polygonShape.SetAsEdge( b2Vec2(-15,0), b2Vec2(15,0) ); //ends of the line
b2Body* staticBody = m_world->CreateBody(&myBodyDef);
staticBody->CreateFixture(&myFixtureDef); //add a fixture to the body

注意:SetAsEdge创建多边形的方法已经在Box2D的v2.1.2版本之后取消了,因为这种单一的线形状已经给出了形状类型,b2EdgeShape。可以使用b2EdgeShape画出相等图形:

b2EdgeShape edgeShape;
edgeShape.Set(b2Vec2(-15,0), b2Vec2(15,0));

myFixtureDef.shape = &edgeShape;

密度(Density)

到目前为止,你可能在纳闷,为什么我用鼠标光标拖拽物体的时候,物体不会像期望中的那样旋转呢?那是因为我们还没有为定制器设置任何密度值。物体的面积乘以密度然后变成定制器的质量。返回到我们第一次声明定制器的地方,然后设置密度值如下:

b2FixtureDef myFixtureDef;
...
myFixtureDef.density = 1; //new code

既然所有物体都使用了相同的定制器进行了定义,它们所有都会受到同一个定制器所影响。

在这个例子中,所有定制器都有相同的密度,那就意味着它们的重量是由它们可见的形状所决定的,其中圆弧是最轻的一个。试着通过改变定制器的密度,看看如何设定物体的‘重量’。记住这是由于所有的物体都使用了相同的定制器,另外你还要确保每次调用CreateFixture方法之前设置定制器的相关属性。

多个定制器(Multiple fixtures)

我们可以把这个三个定制器附加到同一个物体上,而且看起来非常简单-回到每次调用CreateFixture方法的地方,把对dynamicBody1,dynamicBody2,dynamicBody3所做的附加操作全部更改到dynamicBody1上。

正如你所看到的这会让所有定制器关联到dynamicBody1物体上。很直观的看到所有定制器附着在它们的物体上,但是并不是必须一定要像这样。定制器可以被添加到物体的任何位置,甚至它们之间可以产生空隙。例如像之前一样,可以让它们之间产生同样的间隔,我们可以返回之前创建它们的位置,调整它们之间的位置。

//for the custom polygon, add 10 to each x-coord
vertices[0].Set(-1 +10,  2);
vertices[1].Set(-1 +10,  0);
vertices[2].Set( 0 +10, -3);
vertices[3].Set( 1 +10,  0);
vertices[4].Set( 1 +10,  1);
...
//for the box, use an extended version of the SetAsBox function which allows
//us to set a location and angle (location is offset from body position)
polygonShape.SetAsBox(2, 1, b2Vec2(20,0), 0); //moved 20 units right, same angle

运行的结果看起来像下面这个样子(这张插图很好的表现了调整后的定制器)。

摩擦(Friction)

现在我们知道了如何为同一个物体设置多个定制器(fixtures),让我们再次以一个空场景做为开始,在下面实验中为一个物体设置四个矩形定制器。

FooTest() {
    //set up a dynamic body
    b2BodyDef myBodyDef;
    myBodyDef.type = b2_dynamicBody;
    myBodyDef.position.Set(0, 20); //middle
    b2Body* dynamicBody = m_world->CreateBody(&myBodyDef);

    //prepare a shape definition
    b2PolygonShape polygonShape;
    b2FixtureDef myFixtureDef;
    myFixtureDef.shape = &polygonShape;
    myFixtureDef.density = 1;

    //add four square shaped fixtures around the body center
    for ( int i = 0; i < 4; i++) {       b2Vec2 pos( sinf(i*90*DEGTORAD), cosf(i*90*DEGTORAD) ); //radial placement       polygonShape.SetAsBox(1, 1, pos, 0 ); //a 2x2 rectangle       dynamicBody->CreateFixture(&myFixtureDef); //add a fixture to the body
    }

    //make a static floor to drop things on
    myBodyDef.type = b2_staticBody;
    myBodyDef.position.Set(0, 0); //middle, bottom
    b2Body* staticBody = m_world->CreateBody(&myBodyDef);
    polygonShape.SetAsEdge( b2Vec2(-15,0), b2Vec2(15,3) ); //slightly sloped
    staticBody->CreateFixture(&myFixtureDef); //add a fixture to the body
  }

在这个场景中,你会开到物体沿着斜坡滑下来。当定制器和地面接触的时候,定制器中的摩擦系数会用来计算物体的滑动速度。摩擦系数的大小可以设置在0~1之间,0意味着完全没有摩擦。当两个物体发生碰撞接触的时候,摩擦的结果往往是向摩擦系数低的方向运动。

试着给定制器定义零摩擦系数:

myFixtureDef.density = 1;
myFixtureDef.friction = 0; //new code

这次物体的旋转会小很多而且根本不能在斜坡上停留,在下滑的过程中速度没有任何损耗。现在

试着把摩擦系数的值设置为1进行比较。最终,说明摩擦系数是定制器的独立属性和物体自身无关,那么为每一个定制器设置不同的摩擦系数如何呢?-在之前CreateFixture方法中添加一行如下代码:

myFixtureDef.friction = i/4.0;

通过对上面物体的演示,你需要设置每个定制器的摩擦系数。

*注意:摩擦系数为1并不能保证每次都没有滑动出现。

恢复(Restitution)

恢复衡量了定制器‘弹性’的大小。像摩擦一样,恢复系数的也控制在0~1之间,0意味着定制器根本没有弹性,1意味着无能量损失,全部反弹回来。当两个定制器发生碰撞,碰撞的反弹的结果方向总由恢复系数更高的定制器决定。

可以像上面设置摩擦系数值一样对恢复系数做一些实验。为每一个定制器设置不同的恢复系数。

myFixtureDef.friction = ...;
myFixtureDef.restitution = 0; //new code

*注意:恢复系数为0并不总是意味着没有反弹

*注意:现实中少量的能量会在反弹过程中损耗

运行中改变定制器属性

有时候在游戏中,你可能希望在某个事件发生时修改定制器的属性。你可以通过定制器中的setter方法来改变摩擦系数,密度系数以及恢复系数。如果你已经有了那个需要改变的定制器的引用,那么你可以轻易的像这样进行改变:

fixture->SetDensity( ... );
fixture->SetRestitution( ... );
fixture->SetFriction( ... );

如果你只有物体的引用,你需要像下面代码实例那样遍历物体的所有定制器来找到你想要改变的那个定制器。

*设置密度需要有一个额外的操作步骤,当你想要让修改产生效果时,首先通过定制器的SetDensity()方法进行值的设置,然后需要调用定制器所附加的物体上的ResetMassData()方法才能产生效果。

遍历物体的定制器

如果你有一个物体,并且想查看附加在物体上的所有定制器,你可以像下面这样做。GetFixtureList()方法返回物体上定制器链表的第一个元素。

for (b2Fixture* f = body->GetFixtureList(); f; f = f->GetNext())
{
    //do something with the fixture 'f'
}

如果你知道物体上只有一个定制器,你只要获取定制器链表的第一个元素就可以了:

b2Fixture* f = body->GetFixtureList();
//do something with the fixture 'f'

清除

如果你想要从一天物体身上移除定制器,那么就调用物体自身的DestroyFixture方法:

b2Fixture* myFixture = dynamicBody->CreateFixture(&myFixtureDef);
...
dynamicBody->DestroyFixture(myFixture);

记住做完此操作之后就不要在使用此定制器的指针!如果你销毁了物体,那么附加在物体上的所有定制器也会被销毁,同样也不要在使用定制器的指针。

通常情况下,如果你了解游戏的逻辑,并且了解物体是如何被销毁的,就可以合理安排程序尽量避免使用野指针。不过即便你工作的是一个复杂的项目,你也可以通过使用Box2D中提供的“destruction listener”特性来轻松搞定。这个特性会在定制器每次销毁的时候通知你,然后你就知道不要再使用这个定制器了。相关方法可以在用户手册的“Loose Ends”一章中查看“Implicit destruction”的使用(译者注:这里给出站内翻译版链接)。
摘自:http://www.ohcoder.com/post/2012-06-13/40028695873