Oh!Coder

Coding Life

Box2D C++ 教程-连接器-概述

| Comments

声明:本文翻译自Box2D C++ tutorial-Joints-overview,仅供学习参考。

连接器

Box2D中有大量的’连接器(joints)’用来把两个物体连接到一起。这些连接器可以模拟物体之间的相互作用,以此形成铰链,活塞,绳索,车轮,滑轮,汽车,链子,等等吧。学习使用连接器可以高效的帮你创建更吸引人和有趣的场景。

让我们快速浏览一些有效的连接器,然后回顾一下这些特性,最后我们将用其中几个常用的特性完成一个例子。这里是Box2D v2.1.2版本中的一些连接器:

  • 旋转连接器(Revolute)-一个允许物体围绕公共点旋转的铰链顶点的连接器
  • 距离连接器(Distance)-在每个物体上都有一个点以保持之间距离的连接器
  • 平移连接器(Prismatic)-两个物体之间的相对旋转是固定的,它们可以沿着一个坐标轴进行平移
  • 线性连接器(Line)-由旋转和平移组合而成的连接器,对于构建汽车悬架模型很有用
  • 焊接连接器(Weld)-可以把物体固定在相同方向上。
  • 滑轮连接器(Pulley)-每一个物体内部都有一个点,通过世界当中某个固定的点,让围绕这个点的物体之间保持一定的距离,物体之间总的距离是固定的,就像是(sheesh…貌似不太容易找到简明的解释来描述)(译者注:连接器的定义也可以参考Box2D手册,连接器章节
  • 摩擦连接器(Friction)-降低两个物体之间的相对运动
  • 齿轮连接器(Gear)-控制其它两个连接器(旋转连接器或者平移连接器),其中一个的运动会影响另一个
  • 鼠标连接器(Mouse)-点击物体上任意一个点可以在世界范围内进行拖动

v2.1.2版本之后添加的连接器:

  • 滚轮连接器(Wheel)-重新定义了一下线性(Line)连接器
  • 绳索连接器(Rope)-在最大的一个范围内强制约束每一个物体

在源代码中,真实的连接器类的命名类似于b2RevoluteJoint,b2DistanceJoint,等等。

创建连接器

连接器的创建方式与物体(bodies)和定制器(fixtures)的创建类似,首先设置一个’definition’,然后用这个设置创建一个对象。创建出来的连接器实例由Box2D世界(world)进行脱管,当完成所有需要做的工作之后告诉Box2D世界(world)进行销毁。这一系列步骤目前对于我们来说已经不新鲜了,概括到此为止,下面让我们看一下典型的创建一个连接器的步骤(xxx代表上面提到的连接器的名字):

1
2
3
4
5
6
//set up the definition for a xxx joint
b2xxxJointDef jointDef;
jointDef.xxx = ...;

//create the joint
b2xxxJoint* joint = (b2xxxJoint*)world->CreateJoint( &jointDef );

虽然整体看起来创建方法大同小异,但是有一点和创建物体以及定制器不一样的地方。让我们回顾一下:当我们创建物体(body),使用CreateBody方法返回一个b2Body*,类似的使用CreateFixture方法返回一个b2Fixture*对象。以此得到一个带有不同定制器的物体,等等。静态或动态我们需要在定义的时候设置合适的属性,然后我们可以得到一个带有不同特性的b2Body*对象。即便是带有不同特性,物体的行为也是非常类似。

另一方面,连接器之间的行为也会有很大的不同,事实上有很多,所以它们实现起来都使用分开的类型来定义的,例如b2RevoluteJoint,b2DistanceJoint等等。类b2Joint是所有上述连接器类型的父类,但是此类型是一个抽象类,永远都不能直接使用。所以当创建连接器的时候,虽然CreateJoint方法看到的是返回了一个b2Joint*指针类型,但是这个指针类型其实指向的是上面提到的连接器的实例。

这也是为什么对于使用CreateJoint方法返回的值,像上面所看到的那样使用强制类型转换,转换成一个b2xxxJoint*类型。当然了,如果你不需要保存连接器的一个引用,你可以只调用CreateJoint方法而根本不用对此方法的返回值进行存储。

定义连接器-通用设置

虽然每一个连接器都有不同的行为,你或许已经猜到它们还有一个共同的父类,它们之间还是有一些共性的。下面是所有连接器共同点的定义:

  • bodyA-连接器连接的物体之一(必需!)
  • bodyB-连接器连接的物体另一个物体(必需!)
  • collideConnected-指定两个连接的物体之间是否可以发生碰撞

其中两个物体的设置很明显就是连接器当中的连个。在有些情况,确实需要区分物体哪个是哪个,这取决于连接器类型。详见各个连接器的具体描述。

对collideConnected属性的设置,可以决定连接器中的两个物体是否还遵循普通的碰撞规律。例如如果你正在做一个破布娃娃,或许希望让布娃娃的大腿和小腿通过一小部分重叠,然后通过膝盖连接到一起,此时你可能需要设置collideConnected属性为false。如果此时你想完成一个电梯,你可能希望电梯能够和地面有碰撞反应,那么collideConnected参数应该设置为true。collideConnected属性默认设置为false。

实现这一点,代码很简单,这里举一个小例子:

1
2
3
jointDef.bodyA = upperLegBody;
jointDef.bodyB = lowerLegBody;
jointDef.collideConnected = false;

定义连接器-具体设置

在对连接器做了通用设置之后,下一步就需要根据当前的情况对连接器做一些详细的特定设置了。这部分通常包括每个物体自身的锚定点,可移动的限制范围,以及马达(motor,译者注:Box2D手册第八章连接器里把motor翻译成了马达,保持统一这里也译成马达)的设定。关于这些内容,既然不同的连接器之间有细微的不同,还是需要详细的探讨的,但是这里先总体看下基本概念。

  • 锚定点(anchor points)
    通常情况下,每个物体会围绕自身周围的位置设定此点。根据连接器的类型不同,此点决定了物体的旋转中心,和连接器的位置保持一定距离的中心坐标点,等等。

  • 连接器限制(joint limits)
    可以对旋转和平移连接器指定限制,此限制表明了物体可以旋转或滑动的范围。

  • 连接器马达(joint motors)
    旋转,平移以及线性(滚轮)连接器可以设定马达,这也就意味着物体不是平白无故的旋转或者滑动的,就好像连接器自身好像有动力源似的。马达设定最大的力或扭矩,用来让旋转或滑动达到之间相对的目标速度,如果速度为0,马达的作用像是在刹车,因为它的目标就是降低物体之间动力。

详见本话题最下面有对特定连接器讨论的链接。

控制连接器与得到反馈

当一个连接器创建之后,根据特定的行为特性可以对马达的速度,方向或者力的大小,以及对限制的打开和关闭。这对于创建一些有趣的场景和好玩的玩意儿非常有用,比如说,轮子可以驱动汽车,电梯需要移上移下,吊桥可以打开和关闭。

你还可以获取连接器的位置信息,移动速度等等。在你的游戏逻辑中使用这些活动的连接器非常有帮助。你还可以获取连接器作用于物体上并保持物体正确位置的力矩和扭矩,如果当前连接器用非常大的力保持当前状态,而你希望连接器打破这种状态,那么这些信息对你来说很有帮助。

清理

有两种方法可以清理连接器。第一种与物体和定制器的删除方法一样:

1
world->DestroyJoint( myJoint );

另一种方法不是很明显,当连接器其中一个相关的物体销毁的时候,此连接器也会销毁。连接器上不存在一个没和任何物体连接的点。这就意味着如果你在模拟过程中改变连接器,需要时刻清楚你删除的物体和连接器的次序。举例来说,需要注意这不同的情况:

1
2
3
4
5
6
7
8
9
//'myJoint' connects body1 and body2

//BAD!
world->DestroyBody( body1 ); // myJoint becomes invalid here
world->DestroyJoint( myJoint ); // crash

//OK
world->DestroyJoint( myJoint );
world->DestroyBody( body1 );

通常来说,如果你了解游戏逻辑,以及连接器什么时候创建和物体如何销毁,那么你应该规划一下清除方式,以避免上面第一种情况。不管怎么说,如果你所作的项目很复杂,或许你会发现Box2D提供的’destruction listener’特性使用起来比较容易。当连接器销毁的时候你会接到通知,之后你就知道这个连接器以后不再使用。具体可以查阅用户手册里’其它’章节中的’隐式销毁‘一节。

连接器位置的问题

有时候你会发现一个连接器很难保持两个锚定点在同一个位置。这么做会被连接器纠正。连接器会计算把物体推回到右对齐所需的冲量,并且冲量会作用到每一个物体,较重的物体受的冲量少,较轻的物体受的冲量多。

如果没有出现其它限制还好(例如,只能在两个物体之间发生交叉),然后物体根据冲量作出正确的移动。不管怎样,当有另一个约束作用时,冲量平衡就会被其它冲量的作用所打乱,通常情况下会作用在两个较轻的物体上。通常可以看作是一个轻物体有两个较重物体,或者与其进行连接或者简单的按下弹起。事实上,类似相同的问题可以被看作是简单点的碰撞冲突,两个明显很重的物体把一个轻物体夹在中间,其实这个问题并不完全是关于连接器的问题。

下面是典型的例子:

pic

虽然左边的例子没有连接,相同类型的计算依然会对定制器的重叠进行解析。每种情况下来看受力分析,展示了轻物体真的被夹在了石头和硬地面之间。

pic

因为它是轻的,被大部分可纠正的冲量所影响,而且上面重一点儿的物体根本就不能被纠正。结果就是会花费很多时间步长来解析这种失准(mis-alignment)(或者说是每次碰撞过程产生的重叠)。另外一个经常发生的场景是有一根很轻的链子,然后让一个重物体附加到绳子的一头-在这个例子中轻物体’拉伸’了,而不是上面所示的压扁的情况,但是这引起的问题和上面一样。

避免这个问题最简单的办法就是必须让相互作用的物体必须有相近的质量。如果说这种假设看起来不合乎情理,现实世界中没有绝对的完美,所以我们永远都不会看到这种假设发生,并且理想情况下,我猜中间的物体应该会被压碎,但是想要用程序实现这种效果却是一件棘手的事情。对于在轻链子的一头悬挂重物体的情况,在真实的世界结果要么是一段被拉伸要么是一根破损的链条(幸运的是这对于程序实现的话不是很苦难)。

如何使用Box2D中连接器的细节

虽然连接器有一些相同的特点,但是他们之间的不同也足够覆盖一个话题了。详细的可以查看下面关于特定连接器的细节。

Comments