概念

首先区分一下两种动画类型:

  • in place:不带位移的动画
  • root motion:自带根位移的动画

root motion 的好处:避免角色动画和实际位移不同步的问题,在实际游戏上面反应出来就是一个移动脚底抹油的效果。

使用

勾选对应动画当中的 Apply Root Motion 选项。

勾选 Apply Root Motion 之后到底发生了什么

首先我们要知道:动画文件里面记录的是物体的绝对坐标和方向,unity 会根据每个 key 帧记录的值,去直接改变游戏对象的坐标和方向,当然每一帧的值是靠插值得出。在动画播放的下一个循环开头时,又将其坐标和角度值直接修改为初始值。

当使用了 root motion 之后,unity 会通过动画文件里面记录的绝对坐标和方向,还有游戏对象的缩放比例。其会使用物体上一帧的相对运动计算出相对位移和相对转角,然后根据这个相对位移和相对转角来控制物体移到。等到动画播放的下一个循环开头时,其并不会回到初始化,而是会根据当前位移速度和转向速度继续运动下去。

可以在脚本的 OnAnimatorMove 方法当中去获取到这个相对位移和相对转角

1
2
3
4
5
6
// In MonoBehavior Script
void OnAnimatorMove()
{
transform.position += animator.deltaPosition;
transform.rotation += animator.deltaRotation;
}

注意:脚本中含有 OnAnimatorMove 方法时,Apply Root Motion 会改变为 Handled by Script。同时上面那一段也就是使用代码复现 root motion 的移动。

总结一下也就是:动画文件会直接修改每一帧里面游戏对象的坐标值和角度值,root motion 则通过相对位移和转角来移动游戏对象。

Generic 当中的 root motion 机制

拿我们可以从一些网站例如 mixamo,下载的一些动画举例,打开其 animation 可以看到只会有一些对各个骨骼的描述,当然此处是将 Humanoid 类型动画看成了 Generic。此时就会想到一个问题:如何去描述整个游戏对象位移

解决办法也很好懂,就是选中某一根骨骼,把 Generic 动画对这根骨骼位移旋转的描述当做对整个游戏物体的旋转和位移描述。

我们对 Generic 动画也可以创建一个 avatar,其只包含一个骨骼,也叫做 root node。Root Motion 在 Generic 动画当中指的就是将角色根骨骼的运动应用到整个游戏对象上面。(此处的应用也就是将绝对位移和旋转转换为相对位移和旋转)

关于 root motion 的一些设置

Generic 动画当中的机制和相关配置

选中一个 fbx 文件,在其 animation 设置下面我们可以看到这样一个与 root motion 有关的设置面板:

此处 unity 将 root motion 的运动划分成了三个部分:

  • 绕 y 轴的旋转
  • 沿 y 轴的位移
  • 沿 x 和 z 轴的位移

首先我们来看看 Bake Into Pose 选项,使用最上面绕 y 轴旋转举例。

  • 如果不勾选:根骨骼动画的旋转就当做 root motion 的一部分来处理,也就是实际运行时,根骨骼不旋转,游戏对象旋转。
  • 如果勾选:根骨骼动画的旋转就不当成 root motion 一部分来处理,也就是根骨骼旋转不会应用到游戏对象上面,实际运行也就是根骨骼旋转,游戏对象不旋转。只有对应的骨骼和绑定在骨骼上面的 mesh 或者说 蒙皮 旋转了。

在其右边的 loop match 也会标明勾选/不勾选是否会带来不好的效果。要不要勾选这个选项还是取决于:需不需要动画来驱动游戏对象的旋转。

下一个选项,Based Upon(at Start),此处指的是动画开始的时候对准的方向是哪里:

  • Root Node Rotation:指动画开始时根骨骼对准的地方,在 unity 当中是根据整个动画片段中的姿态计算出来的,一般情况下不准,特别是 mixamo 这种没有专门根骨骼的动画。如果动画有偏差,可以使用下面的 Offset 选项来缓解一下。
  • Original:其是美术制作 3d 动画资源的时候就规定好了的方向,一般情况下是可以相信的方向的。

同理,下面对 y 轴上的位移,如果是一个跳跃动画,不勾选的话游戏对象就会一起跳跃,勾选的话只有模型外观在跳跃。比如一些 Idle,Walk 这种不希望 y 轴方向上发生任何位移的,我们就可以将其勾上 Bake Into Pose

Humanoid 动画当中的机制和相关配置

在 Humanoid 动画当中,由于使用了 avatar 系统,动画文件不再会包含对于某个具体骨骼的描述,因此我们也无法像 Generic 动画那样指定根骨骼来应用 root motion。

为了解决这个问题,unity 会在 humanoid 动画中分析其骨骼结构,计算出模型的重心,也就是 Center of Mass,也可以被称为 body transform。

可以在动画预览窗口中看到,人物腰部的地方有一个蓝点,其就是重心(比较靠近人物的 Hip,计算还是比较准确的)。同时我们可以使用以下方式在脚本当中访问其位置和方向:

1
2
3
4
5
6
7
8
private void OnDrawGizmos()
{
Gizmos.color = Color.blue;
// 人物重心
Gizmos.DrawSphere(animator.bodyPosition, 0.05f);

var bodyRotation = animator.bodyRotation;
}

接下来,unity 会根据动画计算出的重心在水平面的投影,并把这个投影当成 root motion 的根骨骼来对待,这个点被称为 root transform,同样也可以使用如下代码来访问其位置和方向:

1
2
3
4
5
6
7
8
private void OnDrawGizmos()
{
Gizmos.color = Color.blue;
// 人物重心
Gizmos.DrawSphere(animator.rootPosition, 0.05f);

var rootRotation = animator.rootRotation;
}

总结一下就是:在 Humanoid 动画当中,unity 会计算出一个 root transform,root motion 会将动画文件当中描述的 root transform 的坐标和角度值,转换为相对位移和相对转角,以此来移动游戏对象。由此就可以在不同的骨骼结构上复用同一个 root motion 动画。

其配置继续参照上面那张图(没错,上面那张图反而是 Humanoid 的)

对于其绕 Y 轴选择的 Based Upon

  • Original:预先设置好的方向。
  • Body Orientation:unity 计算出来的重心的方向。

对于其 Y 轴上的位移的 Based Upon

  • Original:预先设置好的方向。
  • Center of Mass:人物重心,即计算出来的 body transform。
  • Feet:脚,也就是 avatar 系统下的脚。(这里设置可以改善一些脚陷入地面的问题)

root motion 与 blend tree 的使用

首先我们准备 IdleWalkForwardWalkBackward 三个动画,并将其放入一个 blend tree。

Threshold 这个阈值,最合适的值其实还是动画自己的移动速度。我们可以使用下面的 Compute Thresholds 让 unity 自动帮我们计算阈值,此处因为只考虑 z 方向上的前后移动,因此选择的是 velocity Z。其实此处的阈值也就是此动画在 Z 方向上的速度。

保持动画移动速度一致

选择之后计算出来了 Threshold,但是此时前进和后退的速度,并不能保持一致。我们想要去同步一下,第一时间肯定是改动其动画播放速度。unity 此处为我们提供了计算动画速度的办法,也就是下面的 Adjust Time Scale

我们可以将 Idle 动画暂时从 blend tree 当中删除之后再计算,这样会比较准确。如下图所示,调整过后,人物前进后退的速度基本上就一样了。

此时速度还是由 root motion 控制,而不是我们想它多快就多快,此时可能会想到,比如我们想要其移动速度为 1,那么直接在对应的移动速度后面改为 1 / 1.548909 即可,unity 会自动计算。

但是实际上还有很多问题,比如我们的 Asuka_WalkForward 动画,其播放速度为 1 时,root motion 移动速度也可能不是 1.548909。因为 humanoid 动画是在不同的骨骼上面去复用,同样的动画,在比较大的模型速度肯定会快一点,在比较小的模型速度肯定会慢一点。1.548909 只是针对原本的骨骼模型的速度。

不同角色复用动画移动速度不一样解决办法

首先一个很简单很暴力的思路,就是将每个角色的 animator 状态机分开管理。但是此处又来看一看使用一个状态机的思路。

直接上代码:

1
2
// in start
animator.speed /= animator.humanScale;

可以访问 humanScale 来知道当前 unity 对这个模型的缩放。如果我们只需要在 blend tree 里面去做这个速度改变的话,只需要改动其 Multiplier 即可,设置单个参数去改变,动画播放速度最终是 SpeedMultiplier 的乘积。

自定义移动速度