Unity3D的Mecanim动画系统可以直接复用3DS MAX中制作的动画文件中的位移,这个就是通过applyRootMotion来达成的,我们只需要在使用Animator控制动画播放的同时,设置Animator的applyRootMotion字段为True就OK了。
那么怎么来利用这个特性达成我们想要的一些效果呢?这个applyRootMotion到底指的是啥呢?
ApplyRootMotion,从字面上理解来看,是『应用根节点的运动』,听起来貌似像那么一回事。可是我们可以从官方文档上看到这样一段话:
The Root Transform is a projection on the Y plane of the Body Transform and is computed at runtime. At every frame, a change in the Root Transform is computed. This change in transform is then applied to the Game Object to make it move.
翻译过来的意思,应该是这样的:
根节点的运动变换其实就是整个物体运动变换通过Y轴垂直在水平面上的一个投影。根节点的运动变换在动画的每一帧中都会进行计算。计算出来的根节点变换结果都会应用在播放动画的对象上,让该对象按照根节点的运动变换进行移动。
这段话大体的意思就是,RootMotion这个玩意就是作用于动画物体在X轴和Z轴上的位移的,而且这个位移是根据实际播放的动画中每一帧物体的位移在X和Z轴上投影计算出来的。
这个特性非常赞特别是对于某些技能动画,整个动画是有一定位移的,但是动画的位移是动作设计师在设计时根据动作需要调出来的,位移是跟动作的幅度直接相关和匹配的。
那么在释放技能的时候就只需要直接播放动画,并且应用这个Root Motion的特性就可以很好的完成角色在播放攻击动作的同时进行移动,动作播放完毕之后就在动画结束帧角色所在的位置,切换为待机动作就OK了。
看起来很牛逼的样子对不对?是的,确实很牛逼。但是还有很多事情需要我们都一一了解以后,我们才能做出我们想要的东西的。
下面我们先岔开一下话题,好好说说这个Animation Import Settings中『Animations』Tab页中各项设置的作用。
- Import Animation,勾选这个才可以导入动画到Unity工程中;
- Bake Animations,这个选项只在使用Humanoid动画并且使用到了IK特性的时候才可用;
- Anim.Compression,这个是关于动画压缩选项的,默认会选择Keyframe Reduction这个是『压缩关键帧』,就是Unity会自行重采样动画的关键帧,还有两个选项『Off和Optimal』,一个是关闭动画压缩,一个是最优化压缩(应该是压缩效率最高,动画效果失真度可能也较高)
- 选择了Keyframe Reduction或者Optimal压缩选项,就会有三个用于控制压缩选项的系数配置, Rotation Error,Position Error和Scale Error,这个三个参数默认都是0.5,越小呢精度就越高也就是说动画的失真度越小。
- Clips,这个下面列出了这个FBX文件下包含的所有动画,我们在默认的动画文件基础上新建和删除动画片段(Animation Clip),当然每个动画片段都是可以指定起始帧和结束帧的; 以下的设置都是针对单个动画片段滴:
- Loop Time,勾选这个选项之后,如果Animator处于播放这个动画状态时,在播放完第一遍这个动画片段之后,会自动循环从起始帧再次开始播放动画,如此循环往复。如果我们不勾选这个选项,例如Animator一直处于播放这个动画的状态,那么动画会定格在动画的结束帧,直到我们通过Animator切换这个Animator状态机的状态,切换到其他的动画;
- Loop Pose和Cycle Offset,在勾选了Loop Time之后生效的两个选项,Loop Pose用于控制动画循环播放时,从结束帧切换到起始帧时,动画的动作可以无缝的衔接上,Cycly Offset就是用于控制循环的时候起始帧偏移用的;
- Root Transform Rotation,根节点的旋转信息
- Bake Into Pose,勾选后会将根节点每一帧的旋转方向信息烘焙到动画的骨骼运动中,在整个动画播放的过程中,根节点的旋转信息就不会在通过Root Motion作用到播放该动画的GameObject上了,这就意味着这个动画播放的过程中,该物体的Transform中的Rotation值不会因为动画中物体做了任何旋转而发生改变,而是会保持一个恒定的值,和该动画播放之前的旋转值保持一致;
- Based Upon (at Start)或者Based Upon,根节点旋转的参考基准,有两个选项『Original和Root Node Rotation』这两个分别指的是动画文件中指定的旋转值和根节点旋转信息,其实我更愿意将Original理解为动画中原点的旋转值,因为在整个动画播放的过程中,所有骨骼肯定都会有旋转和位移的变换,但是动画的原点其实一定都是确定的,这样理解感觉更简单也更形象一些,勾选了Bake Into Pose之后,就会变成Based Upon而不勾选Bake Into Pose就会保持为Based Upon (at Start),这个目前还木有理解为啥;
- Offset,旋转角度与参考基准的偏移(以度为单位);
- Root Transform Position(Y),根节点位移信息(Y轴)
- Bake Into Pose,勾选后会将根节点每一帧在垂直Y轴方向上的运动信息烘焙到动画的骨骼运动中,在整个动画播放的过程中,根节点在Y轴方向的所有位移信息不会通过Root Motion作用到播放该动画的GameObject上,这就意味着我们在场景中看到物体在Y轴上有位移,例如向上或者向下移动,但是该物体的Transform中的Position信息不会发生改变,会跟动画播放之前的Position信息保持一致;
- Based Upon或者Based Upon (at Start),这个貌似有点不一样哦,在选中Bake Into Pose之后会变成Based Upon (at Start),不勾选的时候是Based Upon,不过这个就能理解了。不烘焙的话,那么Root Motion中Y轴的变化就依赖于选择的『Original或者Root Node Position』的Y轴位移变化,如果选择烘焙的话,那么就以这个动画的起始帧的Y轴作为整个动画Root Motion的Y轴位移,在整个动画播放的过程中,Y轴的位移都是恒定不变的;
- Offset,垂直方向上的偏移;
- Root Transform Position(XZ),根节点位移信息(水平面,XZ轴)
- Bake Into Pose,勾选后会将根节点每一帧在水平面(X和Z轴)方向上的运动信息烘焙到动画的骨骼运动中,在整个动画播放的过程中,根节点在X和Z轴方向的所有位移信息不会通过Root Motion作用到播放该动画的GameObject上,这就意味着我们在场景中看到物体在水平面上移动,但是该物体的Transform中的Position信息不会发生改变,会跟动画播放之前的Position信息保持一致,假如动画中物体会向前移动3米,我们会看到物体在整个动画播放过程中确实在向前移动,播放到最后一帧时确实向前移动了3米,但是当这个动画播放完毕之后,切换到任何其他的动画时,物体会直接闪回这个动画播放前物体所在的位置,所以通常我们需要保留动作位移的动画都不会勾选这个选项。那这个选项有神马用捏?例如某些待机动画,我们其实希望物体只是做一个待机动作,但是实际上不想让物体在水平方向上有位移,这个时候就可以勾选这个选项了,到时候看起来物体就像是钉在水平面上了;
- Mask,这个掩码主要是用于控制动画播放过程中,各个骨骼之间的运动变换的
- Definition,可以选择从动画文件创建也可以选择使用其他动画文件中已经创建好的配置;
- Transform,这个就是动画文件中所有骨骼的层级关系,可以选择勾选那些需要应用动画中运动变换的骨骼;
- Curves,这个主要用于设置某些跟动画相关的参数用,例如控制整个动画播放过程中的速度参数之类的,在动画播放的过程中可以通过Animator.GetFloat(ParamName)函数来读取曲线的值,曲线的X轴为动画的时间轴,Y轴为曲线的值,曲线可以通过曲线编辑器进行增加关键点,调整曲线斜率进行编辑,读取时默认会根据当前动画播放的进度作为X轴的值进行读取,一个动画片段可以有多个曲线;
- Events,这个是用于在动画播放的过程中触发事件的,例如整个动画中有起跳和落地两个事件需要在准确的时间点触发并通知到游戏中其他的对象,那么就可以在Events时间轴上新增事件通知,设置好触发的方法名称和参数,在播放该动画的GameObject上确保有某个脚本中有与该事件通知的方法签名一致的方法就好了,当动画播放到触发通知时间时,就会向GameObject广播该时间通知,脚本中方法签名一致的方法就会被回调了,那我们就可以做我们需要做的事情了。
- Loop Time,勾选这个选项之后,如果Animator处于播放这个动画状态时,在播放完第一遍这个动画片段之后,会自动循环从起始帧再次开始播放动画,如此循环往复。如果我们不勾选这个选项,例如Animator一直处于播放这个动画的状态,那么动画会定格在动画的结束帧,直到我们通过Animator切换这个Animator状态机的状态,切换到其他的动画;
说了这么多貌似跟Root Motion不是很相关的东西,那么究竟我们今天的主题是啥呢?肯定还是Root Motion这货。主要因为动画导入时的设置对于Root Motion的应用影响非常直接,所以前面絮絮叨叨地把这个动画导入设置都罗列了一遍。
回到正题,Generic动画应用Root Motion有以下几个特点:
- Root Motion仅仅作用于GameObject在X和Z轴上的位移变换,不影响Y轴上的位移。例如现在播放一个从地上向前空翻之后落地的动画,设置Animator的applyRootMotion为True,也就是应用Root Motion,那么动画在播放过程中,物体会在水平方向和垂直方向上都按照实际动画的运动轨迹进行运动,如果将applyRootMotion设置为False,那么我们就只能看到动画在原地起跳然后再落地,动画中原本应有的在水平方向的位移就没有了;
- Root Motion与导入动画时设置Root Transform Position(XZ)是直接相关的,如果我们选择了将X和Z轴方向上根节点的位移烘焙到动画骨骼运动中的话,那么动画播放过程中不论我们是否将Animator的applyRootMotion设置为True还是False,动画播放过程中物体在X和Z上的移动是一定的,因为这个已经被烘焙到骨骼动画中,只要动画播放,物体就会移动,但是在动画播放的过程中GameObject的Position值不会改变,在动画结束后我们切换到其他动画的时候,其他动画开始播放时的GameObject的位置会回到这个动画播放前的位置,所以如果我们需要对某个动画应用Root Motion的话,那么这个动画在导入的时候就不要烘焙其在X和Z轴方向上的Root Transform Position,让Unity自行根据动画中根节点的位移进行位移计算GameObject的位置信息;
- 注意Root Motion与Rigidbody.Velocity属性的关系,如果有两个动画A和B,播放A动画的时候,希望A动画应用Root Motion,而在播放B动画的时候不想应用Root Motion,那么就直接在切换到动画B的时候,将Animator的applyRootMotion设置为False就OK了。但是如果播放动画的GameObject带有Rigidbody组件,那么需要注意一点,在播放A动画时Rigidbody的Velocity并不会在切换到B动画时清零,也就是说如果A动画的运动速度较快,那么切换到B动画的时候,如果希望B动画播放的时候GameObject按照自己的设定轨迹运动,就需要自行手动在切换到B动画之前将Rigidbody的Velocity属性清零,防止GameObject按照A动画的运动惯性继续运动。这个问题在没有Rigidbody组件的GameObject上不会存在;
这边再岔开一下,说说这个动画跟Rigidbody之间的关系:
- 如果我们没有将Root Transform Position的Y和XZ轴进行烘焙的话,那么在动画播放的过程中,Rigidbody将会自动获得动画中物体运动的速度信息,直接通过Rigidbody.Velocity属性就可以获得;
- 如果我们将Y轴进行烘焙,那么Rigidbody.Velocity在Y轴上的值将会一直为0,对于XZ轴也是一样的,如果烘焙了XZ轴的位移,那么整个动画播放过程中,Rigidbody.Velocity在X和Z轴上的值都会为0;
- 如果播放动画的物体没有Rigidbody组件,那么动画的运动都会仅仅按照动画实际的位移来进行逐帧播放,不会出现上文中提到的动画播放切换之后还存在的运动惯性问题,因为物理引擎依赖于Rigidbody组件,如果没有该组件,所有动画的播放都只是逐帧播放动画,不会存在速度的概念只有移动位移。
- Rigidbody使用使用重力对于动画在Y轴上的位移没有任何影响,不论是否对Root Transform Position的Y轴进行了烘焙。