小言_互联网的博客

黑魂复刻游戏的玩家控制器(播放攻击动画时的位移)——Unity随手记(2021.5.4)

260人阅读  评论(0)

今天实现的内容:

通过代码运用动画自带的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");

    }


BUG以及缺陷:


值得注意的:

代码运用Root Motion在工作中应该挺重要的,毕竟美术们做的Root Motion肯定更好看,而我们程序员要做的就是运用这些Root Motion。



转载:https://blog.csdn.net/qq_37856544/article/details/116399518
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场