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动画中的位移,可以移步这里查看。

Unity3D中useGravity和isKinematic以及applyRootMotion的备忘》有5个想法

  1. beckheng

    感谢回复。
    现在我遇到这么一个问题,在一个竞技场场景里,英雄推动NPC的话,会有几率的把NPC给挤到了墙外。
    英雄是有Character Controler, Capsule Collider, Animator这些组件
    NPC是用了Rigidbody和Capsule Collider,Animator这些组件

    组件用的都是默认的参数,请问这个可以从那个方向来定位问题?

    回复
    1. 古拉是头猪 文章作者

      推出墙外是很有可能的,我们可以自己模拟一个主角自己把自己推出墙外的示例。例如我让主角的运动速度是每米1000米,然后在主角运动的方向上,距离主角2米处摆一堵墙,然后让主角运动一下,主角肯定会出墙的。

      回复
    2. 古拉是头猪 文章作者

      这个原因呢,我们可以认为就是Unity3D的物理引擎计算不够精确导致的,很多的时候还是需要我们自行计算判断是否会越界,而不能完全依赖于Collider自行进行碰撞检测。

      回复

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据