Unity3D 中 useGravity 和 isKinematic 以及 applyRootMotion 的备忘

在最近几天的开发中,碰到一个比较有意思(恶心)的问题,需要达到的效果如下:

  1. 有一个 NPC,在受到攻击时,会播放一个从站立姿势到击飞浮空的动画;
  2. 在 NPC 切换击飞浮空动画的同时,NPC 需要同时向斜上方飞行,飞行到指定高度待机。

通常我们有几个方案可以选择:

  1. 通过 Unity3D 自带的物理引擎模拟,给 NPC 一个斜上方的速度或者作用力,这个通过 Rigidbody.velocity 设置一个速度向量或者通过 Rigidbody.AddFore/Rigidbody.AddRelativeForce 进行设置就 OK 了;
  2. 自己根据需要的速度自行计算每一帧 NPC 的位移,然后通过直接设置 NPC 对象 Transform.position 属性来达成 NPC 斜向上飞的效果。

这两个方案从理论上来说肯定都是 OK 的,其实方案一最终在 Unity3D 内部的 PhysicX 引擎中的实现也是通过对 Transform 的 position 进行设置来完成的,只是额外还会进行碰撞监测和阻力计算等等。

实际开发的过程中,我分别使用了这两种方案来进行测试,对比之后发现:

  1. 方案一模拟效果确实更真实,而且省去了很多的计算过程,但是简单的设置速度和作用力可能无法达到策划设计的受击效果,因为我们制作的是格斗游戏,对于受击对象最终浮空的位置有相对严格的要求,需要为各种连续攻击留足设计空间,这个就不展开说了。总之,如果完全交给物理引擎来演算模拟的话,会出现一些不可控的结果,而那是我们不想要的,所以需要通过一些其他的手段来进行反算,使得我们设置了某些参数之后,物理引擎模拟最终的结果就是我们想要的(最终我就是这么做的);
  2. 方案二简单,而且结果是完全可控的,NPC 飞行的轨迹完全是可以预计的,但是由于直接控制 Transform 的 position 是完全无视在整个游戏场景中的物理碰撞的,在做 position 设置之前每次都需要自行检测 NPC 是否会与场景中可碰撞对象之间的关系,这个性能的开销如何控制以及如何做到有效的检测对于目前的我来说是一个较难的课题,所以最终我放弃了。

那么按照方案一来执行的过程中,又碰到了什么有意思的问题呢?

NPC 在受击之后,通过 Animator 设置了一个 Trigger,触发 NPC 从站立到击飞浮空的动画切换,同时给 NPC 对象的 Rigidbody 一个 Velocity,代码如下:

[code lang=”csharp”]
mAnimator.SetTrigger ("KickFloat");
rigidbody.velocity = velocity; // NPC 斜向上方飞行的初速度
[/code]

可是最终执行的效果是 NPC 只会往上垂直飞行,在水平方向上根本就没有速度,这是为啥呢?就在我快要把头发拽下来的刹那,我决定先去泡一杯劣质的越南『中原 G6』速溶咖啡,等我把咖啡泡好了回到座位上,突然意识到其实在很早之前碰到过另一个问题,当时就是因为 Animator 的 applyRootMotion 变量给惹的祸,卧槽啊~

于是,我很精灵的在刚刚的代码基础上加上了一个前缀处理,代码如下:

[code lang=”csharp”]
mAnimator.applyRootMotion = false;
mAnimator.SetTrigger ("KickFloat");
rigidbody.velocity = velocity; // NPC 斜向上方飞行的初速度
[/code]

尼玛这下清净了吧,哥哥我半个下午的时间就是在不断的怀疑人生和自我啊,不过看在最终还是搞定的份上,我也就不再说啥了。然后 NPC 在做从站立过渡到空中浮空待机的动作的同时,终于按照我设定的速度的方向移动了,如释重负啊。

说了这么老些题外话(什么?这些竟然都是题外话?尼玛这已经很丰富了喂,可以做单独一篇文章了喂,那你到底要说的是啥啊?),我的目的只有一个,我就是想说明一下我为啥要针对这三个变量来说道说道,因为它们真的蛮有意思的(恶心了我这个 Unity3D 傻逼)。为了让自己以后不再傻逼,所以还是记录一下,做个备忘吧。

Rigidbody.isKinematic,这个属性用于控制物理引擎效果是否会在生效,如果这个属性为 True,那么所有通过物理引擎给物体施加外力和设置速度等等就不会再有任何效果了。例如如果想做一个让 NPC 受击浮空之后,在空中停留的效果,就可以通过设置这个变量来实现,只要该变量为 true,物理的重力效果就不会生效,NPC 并不会自动往下做自由落体运动,而会在空中保持之前的动画状态,位置也不会发生改变。

Rigidbody.useGravity,这个属性就是用来控制物理引擎模拟时是否加入重力影响,例如我们在做击飞效果的时候,通常会通过给受击对象一个击飞后方向的作用力或者运动速度,如果需要考虑重力因素的话,可能会更真实一些,但是有的某些情况下,我们可能不想因为重力的作用对受击对象的飞行轨迹产生影响,那么就可以通过将该变量设置为 false 就可以关闭重力在屋里引擎模拟过程中对物体整体运动速度和轨迹的影响了。

Animator.applyRootMotion,这个属性是用来控制物体在播放骨骼动画的时候是否应用骨骼根节点的运动参数,也就是说在动画播放的过程中是否使用动画中物体的位移。在实际制作的过程中,总会有某些动画自身带位移,而某些动画是不带位移的,对于那些动画中自带位移的,那么就可以通过设置这个变量为 true 就可以让对象在播放动画的过程中按照动画中的位移进行移动,这样动画的效果也会更逼真一些而不是通过程序来控制对象的位移(程序大部分情况下都是简单的做匀速或者加速运动,无法做到完全与动画中动作移动幅度完全吻合,可能会出现一些失真的情况)。但是如果一旦设置了这个变量为 true,那么请一定注意,这个会对物理引擎在模拟对象的运动轨迹时产生直接的影响,例如在某个动画 A 中,对象只向 Y 轴方向进行了移动,在 X 和 Z 轴是静止的,那么我们在播放 A 动画的时候,如果使用 Rigidbody 设置速度或者施加外力,还是不会让物体在 X 和 Z 轴上发生位移的。这是因为,在整个动画播放的过程中(例如 0.5 秒),Animator 会根据动画中物体的位移信息对物体的速度进行赋值,这样达到使用骨骼根节点的位移的效果,也就是说,我在播放动画过程中的任意时间给物体设置了 X 或者 Z 轴方向上的运动速度,后续的动画播放帧中,速度又会被 Animator 强制赋值为跟动画文件中的位移信息一致。

综上,咱们来说说碰到哪些情况,我们需要检查哪些变量?

  1. 如果出现物体无论设置运动速度还是施加作用力,愣是没有任何效果,赶紧检查一下 isKinematic 变量是否为 true,如果是的话,那么肯定是不会有效果的,当然也不会自由落体了;
  2. 如果出现物体往某个一直运动,根本停不下来,那很有可能是 useGravity 这个变量为 false,如果真的不想要这个重力效果,那么设置为 false 刚好可以从此摆脱地心引力了;
  3. 如果出现物体无论设置运动速度还是施加作用力,而且也没有设置 isKinematic 变量为 true,那么先别怀疑人生,赶紧确认该物体的 Animator 组件是否设置了 applyRootMotion 变量,否则头发揪没了也无济于事啊。

关于 Mecanim 动画系统中,如何让物体应用 Generic 动画中的位移,可以移步这里查看。