今天实现的内容:
通过代码运用动画自带的Root Motion
在这篇博客之前,攻击时是不会出现位移的,因为我们没有运用动画自带的Root Motion。为了更好的动画效果,我们接下来将会运用到Root Motion。
上图中,红色的标识指示了动画的Root Motion量(具体我也不是太懂,反正就是个量),当我们运用Root Motion时,系统会将Root Motion量套用到游戏对象的transform上,注意是Animator所在的游戏对象,我们的项目中,Animator被放到控制器次级的模型对象上,所以移动的只是模型。
要使用Root Motion,我们需要设置Animator组件下的Apply Root Motion为true,但是如果我们直接就这么做了,那些本身带有Root Motion但是我们并不想要运用的动画也会被用上,比如说移动动画,而且由于我们的模型只是控制器对象的子对象,运用动画的Root Motion会导致模型脱离父对象的原点。为了解决这些问题,我们还是需要用脚本来运用Root Motion。下面的图片展示的就是我刚刚提到的问题。
我们的代码将使用OnAnimatorMove方法(MonoBehaviour.OnAnimatorMove),该方法会在动画机算完Root Motion值之后的每一帧调用(但是还是在IK计算之前)。在这里我们甚至可以对动画机算完之后的Root Motion做修改。为什么是算完之后?因为在算之前修改等于没改。
这次我们不会去修改Root Motion,我们将利用OnAnimatorMove的系统调用时机,首先得到动画机计算的deltaPosition,就是动画机计算出的模型的Root Motion的位移(Root Motion不仅仅是位移还有旋转deltaRotation,但是这次我们只要位移就行),继续发挥传统艺能,在OnAnimatorMove中将deltaPosition,通过发送消息传递给PlayerController,由PlayerController中的OnUpdateRootMotion根据deltaPosition去移动Rigidbody。毕竟Rigidbody在同一级,位移相关的所有操作还是要交给PlayerController。这样一来,我们就通过脚本运用了攻击动画的Root Motion。
public class RootMotionController : MonoBehaviour { private Animator anim; private void Awake() { anim = GetComponent<Animator>(); } void OnAnimatorMove() { SendMessageUpwards("OnUpdateRootMotion", (object)anim.deltaPosition); } }
可以发现当我们用了OnAnimatorMove以后,Apply Root Motion变成了Handled by Script。
下面是PlayerController中的OnUpdateRootMotion具体是如何运用Root Motion的。注意,我们是在每一物理帧中才进行Rigidbody的位置修改,所以在每一个动画帧中要做的事情是累加Root Motion的位移量。
// Root Motion的位移量 用于脚本运用Root Motion private Vector3 m_deltaPos; // 处理刚体的操作 private void FixedUpdate() { // 运用Root Motion 要放到修改rb.velocity以前进行 rb.position += m_deltaPos; // ... // 清零当前物理帧累积的m_deltaPos m_deltaPos = Vector3.zero; } // 通过脚本运用动画的Root Motion // 通过RootMotionController脚本中的OnAnimatorMove调用 public void OnUpdateRootMotion(object _deltaPos) { // 当前处于attack_oneHand_C动画才会运用Root Motion位移 if (CheckState("attack_oneHand_C", "Attack")) { // 更新m_deltaPos为动画机的Root Motion 之所以用累加是因为物理帧和动画帧不一样 在物理帧的最后会将m_deltaPos清零 // 根据我那点可怜的C#基础知识 这一步会导致拆箱 OnAnimatorMove中的那一步会导致装箱 损耗计算资源 m_deltaPos += (Vector3)_deltaPos; } }
最后的关键一步,我们要识别当前处在什么动画状态,只有在攻击时if (CheckState("attack_oneHand_C", "Attack"))才更新Root Motion,这样做完以后,就规避了直接Apply Root Motion带来的问题。
按照之前的办法自制位移
如果动画没有自带位移,我们就只能使用之前曲线加冲量的老办法自制了。但是效果肯定就很难有美术们做Root Motion的效果那么好了,毕竟美术大大们在这方面还是比我们厉害的。
// 在Attack层的动画节点attack_oneHand_B更新时执行的方法 // 通过PlayerController动画机中的attack_oneHand_B节点上挂载的FSMOnUpdate调用 public void OnAttack_oneHandBUpdate() { // 计算攻击时的冲量 m_thrustVec = model.transform.forward * anim.GetFloat("attackOneHandAVelocity"); ; } // 在Attack层的动画节点attack_oneHand_C更新时执行的方法 // 通过PlayerController动画机中的attack_oneHand_C节点上挂载的FSMOnUpdate调用 public void OnAttack_oneHandCUpdate() { // 计算攻击时的冲量 m_thrustVec = model.transform.forward * anim.GetFloat("attackOneHandAVelocity"); }