在Unity3D的Legacy动画系统中应用Root Motion
最近,我仔细比较了Unity3D当前版本中的两套动画系统:Legacy和Mecanim。Mecanim系统的功能比Legacy强大很多,但使用AnimatorController着实不太方便(尽管使用AnimatorOverrideController可以避免重复编辑状态机)。这是因为在游戏逻辑层面,通常需要用一个状态机或类似机制来控制角色的状态,而角色层面的状态逻辑和动画层面无法一一对应,要让两套复杂的状态机配合起来,想想都觉得麻烦。难怪很多朋友至今仍在使用Legacy动画系统。其实,Legacy动画系统的功能也很全面,包括Layer、过渡混合、上下身混合等功能完全能够胜任,而且控制起来更加直接。唯独Root Motion这个我很需要的特性,它没有提供支持。本文将探讨如何在Legacy动画系统之上附加Root Motion功能,其实操作很简单。
何谓Root Motion
在不使用Root Motion的情况下,像走、跑这类位移控制的做法如下:
- 要求美术人员在导出动画时去掉位移。
- 在程序代码里控制角色移动的速度,在播放动画的同时,计算其位移。
这种做法其实不太科学。程序控制的角色只能当作一个质点来处理,并且大多数时候是匀速运动,而动画中角色的移动往往很难与之匹配。所以,需要进行精细的计算并运用良好的美术技巧,才能避免角色出现“滑步”现象。在“跑”这种快速移动的情况下,滑步问题相对容易处理,但如果是慢速移动,再厉害的美术人员也可能无能为力。在这种情况下,最好使用Root Motion:
- 美术人员在导出动画时附带位移。
- 程序从动画中读取每一帧的位移,并将其应用到角色上,这样就能实现动画和位移的完美匹配。
在Legacy中添加Root Motion功能
了解了Root Motion的概念后,在Unity3D引擎中实现此功能并不复杂。Unity3D拥有统一的对象层次结构设计,这一点非常出色,我们可以很容易地找到角色的根骨骼,然后读取其中的Transform变换。以下是示例代码:
//-- 计算当前帧的Root Motion
Vector3 rootPos = m_rootBone.localPosition;
m_rootMotion = rootPos - m_lastRootPos;
m_lastRootPos = rootPos;
rootPos.x = 0;
rootPos.z = 0;
m_rootMotion.y = 0;
m_rootBone.localPosition = rootPos;
请注意,由于我们要在后续代码中将m_rootMotion应用到角色对象上,所以将m_rootBone的位置重置了。
读取到当前帧的Root Motion后,就可以将其应用到当前对象上:
//-- Apply Root Motion
Vector3 nextPos = this.transform.position + m_rootMotion;
this.transform.position = nextPos;
另外,有一个细节需要处理,在动画循环的那一帧,需要进行特殊处理。下面是完整的源代码:
using UnityEngine;
using System.Collections;
public class ApplyRootMotion : MonoBehaviour
{
public Transform m_flagObject; // 用来测试位置的一个对象
//-- Root Motion 控制变量
Transform m_rootBone;
Vector3 m_lastRootPos;
Vector3 m_rootMotion;
int m_lastAnimTime;
void Start ()
{
//-- 从SkinnedMeshRenderer中读取Root Bone
SkinnedMeshRenderer skinMesh = this.gameObject.GetComponentInChildren<SkinnedMeshRenderer>();
m_rootBone = skinMesh.rootBone;
//-- 变量初始化
m_rootMotion = Vector3.zero;
m_lastRootPos = m_rootBone.localPosition;
m_lastAnimTime = 0;
}
void Update ()
{
//-- Apply Root Motion
Vector3 nextPos = this.transform.position + m_rootMotion;
this.transform.position = nextPos;
//-- 测试代码:更新测试物体的位置
Vector3 flagPos = m_flagObject.position;
flagPos.x = nextPos.x;
flagPos.z = nextPos.z;
m_flagObject.position = flagPos;
//-- 测试代码:更新摄像机
Camera.main.transform.LookAt(this.transform);
}
void LateUpdate()
{
AnimationState animState = this.animation["walking"];
if ((int)animState.normalizedTime > m_lastAnimTime)
{
//-- 动画循环处理
m_lastRootPos = m_rootBone.localPosition;
m_rootMotion = Vector3.zero;
}
else
{
//-- 计算当前帧的Root Motion
Vector3 rootPos = m_rootBone.localPosition;
m_rootMotion = rootPos - m_lastRootPos;
m_lastRootPos = rootPos;
rootPos.x = 0;
rootPos.z = 0;
m_rootMotion.y = 0;
m_rootBone.localPosition = rootPos;
}
m_lastAnimTime = (int)animState.normalizedTime;
}
}
最后附上截图,但静态图片无法展示效果,你可以下载完整Demo(请使用Unity 4.6版本打开),其中角色移动非常平滑,不会出现滑步现象。
请移步百度网盘下载Demo:http://pan.baidu.com/s/1o6kJsIe,密码:osoc。