Unity Joint 用法及案例
本篇文章主要讲解如何在 Unity 中使用 Joint 组件完成一些刚体物理之间的连接效果,并且讲解一个简单案例。
什么是 Joint
官方文档介绍 Joint 可以连接一个刚体与 另一个刚体 或世界空间某点,Joint 可以通过施加力的方式来限制运动,joint 中文翻译可以叫 约束。在力学观点下,运动分为 6 个自由度,沿着 xyz 轴的位移,绕 xyz 轴的旋转。通过将某些轴的位移和旋转给一些限制条件来达到限制刚体运动的目的。获取地址:http://www.jnpfsoft.com/?from=infoq

Unity 的 Joint 实际就是调用了 NVIDIA PhysX 的 PxConstraint。
有哪些 Joint
PhysX 提供了 Fixed Joint,Character Joint、Hinge Joint、Spring Joint 等等。这些种种的 Joint 其实都是基于通用的 Configurable Joint 调配参数封装出来的,比如 Fixed Joint 就是将 6 自由度运动全部配置为 lock,两只刚体加上了这个约束就会绑定在一起,没有任何相对运动。

通过点击这个按钮我们可以看到一些用来调节 joint 参数的 Gizmos。
Joint 计算原理
在 Unity 中 Joint 是作为 Component 存在,他必须依附一个 Go,这个被依附的 Go 需要有 rigidbody,Joint 会影响这个刚体,通常我们指定另一个刚体到 ConnectedBody 上表示另一个被 Joint 影响的刚体,如果想要这个 joint attached 刚体被固定在空间点上,那么 conectedBody 可以不填。
自由度配置
Joint 拥有 3 个位移自由度和 3 个旋转自由度。相应的,在 Configurable Joint 中对应如下几个参数:
位移有 - XMotion、YMotion、ZMotion
旋转有 - AngularXMotion,AngularYMotion,AngularZMotion
在这 6 个自由度上,分配有以下三种配置:
ConfigurableJointMotion.Free
ConfigurableJointMotion.Locked
ConfigurableJointMotion.Limited
其中 Free 意味着约束不存在。比如 XMotion Free,代表了两个刚体在 X 轴向可以相对随意移动。
Locked 则表示刚性约束,比如 XMotion Locked 代表了两个刚体在 X 轴向的相对位置被完全固定不可改变。(但在实际物理模拟中是存在被强行拆散可能性的,例如刚体穿插等等,会导致 Solver 失败)
Limited 则意味着有限范围的偏差。当超出有效范围时,约束将会开始生效,企图使两个刚体回到正常偏差范围内。
Axis 与 Anchor
Joint 默认会以这个 attach 的 GO 的 Transform 坐标系作为 Joint 的六自由度坐标系,如果我们想要一个另外的坐标系,需要填 Axis 和 SecondAxis,他会作为这个 body transform 的 child 而存在。
然后是使用 Joint 是最容易出错的点——Anchor 这里我们画一幅图来说明

cubeA 身上放了一个 Joint 组件,连接着 cubeB,cubeA 的 anchor 放在蓝圈位置,joint 的坐标系的 xy 是红绿线。anchor 实际上有两个,一个连着 A 叫 Anchor,另一个连着 B,叫 ConnectedAnchor,一般我们都会勾选 AutoConfigureConnectedAnchor 让两个 anchor 在一起。从 anchor 到 A 的连线在 joint 游戏开始时 anchor 到重心的距离会被记录,可以看做一个不可变形的刚杆(如果 anchor 在某个 cubeA 的中心那么属于 cubeA 到 anchor 就没有钢杆了),游戏运行时 anchor 和 conectedanchor 会发生相对运动, 以 joint 坐标系为准,这里我们假定没有自己配置 Axis,那么 joint 坐标系就是 A 的坐标系,如果此时我们配置了 Joint 的 XYZ 的 Motion 为 lock,其他 angular xyz motion 为 free,并且把 A 的刚体配置成 Kinematic(让他不受重力下落影响,固定在空中),那么就会得到一根钢杆一头在蓝圈 一头在 cube 的重心的自由旋转运动,拽着 A 去甩 B 其实有点像双节棍。
如果我们配置了 angular xyz 为 lock,xyz 的 motion 中某个轴 比如 Y 轴为 free,那么便可以得到沿着 y 轴的滑动一样的效果。

通过对于不同维度的控制我们可以得到很多不一样的效果。
Limit 与 Spring
整个计算流程包括三步 1、计算两物体轴向距离 2、将轴向距离投影到 joint 空间下 3、调节轴向距离满足 joint 参数
如果我们想让两个 anchor 的某些轴向的位移在一个范围里运动,那便可以将这几个轴的 motion 调整为 limit,这种两个 anchor 的距离(注意是距离,算的是两点作差的值)会被限制在 limit 内,如果把 A 固定,B 自由下落那么 B 到了某个点位就会啪的一下卡住,好像撞到了地面一样,实际上是被 Joint 拉住了。如果我们想他在这个边缘位置不要这么生硬的停下而是有弹性、柔和一点的运动,那么可以配置 spring 值。

需要注意的是 xyz 都是共享一个 limit 的,只存在 anchor 之间 按线运动、按圆圈运动、在球体范围内运动三种情况。默认 spring 是 0,表示没有任何弹簧就是一个很刚性的约束(看起来非常硬)。damper 标志了弹簧震动的阻尼系数,阻尼会根据刚体运动速度产生一个反向的作用力,以降低震动的频率。
angular limit 和其 spring 的配置原理也是一样的,只不过是计算轴向距离变成计算两个 rotation 的差异,另外 angularlimit 关于主轴的计算可定制性更强,可以存在最小负角和最大正角,其他两个轴则定义相对限制多。按两个方向分别进行定义是由现实意义的,因为人的很多关节都是这样的。比如你的膝关节、肘关节都只能往一个角度弯曲,而不能反向弯曲。这也决定了当我们使用 Joint 来模拟物理骨骼时,必须正确的分配 Joint 主轴朝向。至于为什么不每个轴都分配最大和最小角度,可能是有数学限制吧,这里博主也不知道。
Drive 与 Target
上述所讲的 anchor 与 anchor 之间的限制主要还是一种受到外力导致运动后 应该怎么限制运动,其实 Joint 还提供了一种内力驱动来让 Joint 对两个刚体产生力,很多的结构比如挖掘机旋转关节,车轮的悬挂系统都会输出一些内部的力对外做功。要配置这些力我们就要用到 Drive 和 Target。首先一般用 drive 的轴的自由度配置都会配成 free,然后通过配置 target position、target rotation 来对两 anchor 连接的刚体施加力。主要分为几个步骤 1、计算两个刚体的坐标差异 2、将差异投影到 joint 坐标系 3、计算差异和配置的 target 的距离 4、为了让距离满足 0,会对两刚体求解施加力
PositionOfBinded 为 Joint 所绑定的刚体 Anchor 位置
PositionOfConnected 为另一个连接的刚体的 Anchor 位置
PositionRelative 为当前两者位置之差,而 TargetPosition 则为预期之差
ErrorOfPosition 为预期和当前的差项,Joint 将根据这个差项产生一个弹性力作用于两个刚体,使得 PositionRelative 在迭代中逼近 TargetPosition
关于 Rotation 差异和 target 计算的部分包括:
RotationOfBinded 为 Joint 所绑定刚体的旋转量
RotationOfConnected 为连接刚体的旋转量
RotationRelative = RotationOfConnected - RotationOfBinded 为两者之差,也可以理解为 Connected 相对于 Binded 的旋转量。
RotationRelativeInited 为初始情况下的 RotationRelative
所有的 Drive,都由以下结构构成:
PositionSpring - 表示驱动力的弹性系数
PositionDamper - 表示驱动力的阻尼系数
MaximumForce - 表示最大驱动力值。即意味着 F 不随着 Error 无限增大
利用以上的定义,将会使用以下公式产生弹性力:
Fdrive=Spring∗E−Damp∗dE/dt
E 代表了当前状态与目标状态之差。
可以用代码运行时去修改 target position、target rotation 来获得一些有趣的物理效果。
Joint 应用案例
讲解一个基于 Joint 的吊车的实现

车轮
旋转
车轮我们使用了 capsule collider,连接在 box 拼接的车身的刚体上然后让 capsule collider 关于轴心方向的轴的旋转是 free 的,其他旋转都 lock,接着我们希望车轮有一点轻微的悬挂系统,如果此时我们把 joint 组件加在了车轮上,使用车轮坐标系,我们会发现 joint 没法找到一个稳定的上方向,这导致我们决定将 joint 加在车身,让 joint 坐标系跟车身保持一致,维持一个相对稳定的 Joint 坐标系。

红色的圆圈表示这个 joint 绑定的东西在 joint 坐标系的 x 轴可以自由的旋转。
这里的 anchor 配置相当重要,anchor 位置为车轮的中心在车身坐标系下的相对位置,这里我通过编辑器写了一点计算代码配置 anchor 位置。
然后我们通过控制车轮的 x 轴角速度便可以实现一个摩擦力驱动的车轮了。
悬挂系统
我们 xyz 的 linear motion 都是 lock 住的,这样遇到一些凸起的路面我们轮子就和车身的相对距离永远不变,这样不符合真实的感觉,真实的感觉应该是轮子会往上有一点抬升,而车身不会有 y 轴方向的运动。
这里我们的 y 轴和车身绑定已经相当稳定,我们可以把 y 轴配置成 free,然后把 yDrive 调大,target position 是 000,其实也就是让这个车轮回归初始位置的意思,这样当车轮支撑车身时候车身会有重力影响产生一定的下沉同时有被举起的感觉,悬空时车轮也会往下掉一点,遇到障碍物是压过了障碍物的轮子会比其他轮子往上抬升一点。

总体运动起来 车身的震动幅度相比没有悬挂系统更小一点。
吊臂
旋转

吊车的大臂可以以转台为中心来做旋转,我们以 y 为上方向 配置 angular y motion 为 free,然后在运行时根据输入来配置 target rotation,可以实现大臂绕着一个点旋转,通过把 drive 和 damp 同时拉大可以获得一个有些重量感的吊臂。
抬升

抬升的做法有一点 trick,对于一般的吊车这里通常是液压的,由下面的浅红色小臂通过液压给力然后把红色的大臂推出去。大臂配置一个绕轴旋转。但是笔者经过尝试,发现这种通过给力的方式在车子运动起来的同时大臂会摇摇晃晃,因为是通过力来作用的,就很容易出现弹簧一样的感觉,并且也不利于控制具体的抬升角度。
于是这里的做法是大臂尾部两个 joint 得到一个绕轴旋转,绕轴旋转的 joint 通过配置 drive 和 target rotation 来让吊臂产生某个角度的抬升。

接着我们制作了一个小臂的一头连着大臂一头连着转台,连着大臂的那端可以沿着小臂轴向有位移,形成了一个动轨滑块结构。

然后让这个小臂对大臂施加一个拉力,这个力往下,而上一个 joint 给的力往上,以此可以减少大臂的摇晃感。

吊钩
伸长
最后是吊钩,吊钩其实就是让这个钩子可以沿着一条轴伸长,始终日常垂直往下,这里我显示给钩子摆了一个垂直往下的姿势,y 轴向上,然后 joint 组件添加在钩子上,anchor 一头绑在吊臂上,一头绑在吊钩 center,允许 y 轴可以 free 运动,用 ydrive 来分离两个 anchor,驱动吊钩往下运动,这样就得到了一个绕着初始点旋转的一根弹性杆,用来做伸长后的吊钩看起来还行。

除了这些外,给一些 angular 的 drive 并且 target rotation 配置成 000,可以让吊钩在空中甩来甩去时更快的回到垂直向下的初始旋转位置。
吸附
最后吊钩要把东西吊起来,可以做一个范围检测,运行时创建一个 lock 的 joint 把钩子和被钩的东西绑在一起,然后再收起吊绳,我们就能吊起东西了!


总结
对绳子不太满意,因为有点弹性杆的感觉,实际上这种拉的紧绷的钢性绳子相当难模拟,拉起时候的感觉也会有弹来弹去的味道,暂时没有想到很好的解决办法
文章转载自: 飞翔的子明
原文链接:https://www.cnblogs.com/FlyingZiming/p/17237420.html#%E8%87%AA%E7%94%B1%E5%BA%A6%E9%85%8D%E7%BD%AE
评论