前言:我们无法监听Animator是否播放完一个动画,我所知的办法就是将监听方法设置为Public,并且挂在带有Animator的物体上,并且还要在Clip文件内新增AnimEvent。于是我自己写了一个AnimCtrler。

需求:开始动画、暂停动画、将动画停止在某一个状态、动画播放完成后执行回调。

思路:

1.播放动画就用Animator自带的API,Animator.Play即可;

2.停止动画,我想要Animator.speed为0达到效果;

3.停止在某一个状态,我们可以先播动画,播放在第一帧时马上暂停;

4.完成回调,使用AnimatorStateInfo.normalizedTime是否>=1f判定

实现:

using System.Collections;using System.Collections.Generic;using UnityEngine;using System;[RequireComponent(typeof(Animator))]public class AnimCtrler : MonoBehaviour{    private Animator m_animator;    private float m_speed;    private string m_curAnimName;    private bool m_clipEndAutoSetNull;    private Dictionary<string, Action<string>> clipEndCallbacks;    public float Speed    {        get => m_speed;        set        {            m_speed = value;            if (m_animator != null)            {                m_animator.speed = m_speed;            }        }    }    public string CurAnimName    {        get => m_curAnimName;    }    ///     /// 回调执行后,是否会自动设置为空    ///     public bool ClipEndAutoSetNull    {        set => m_clipEndAutoSetNull = value;    }    private void Awake()    {        m_animator = GetComponent();        m_speed = 1f;        m_animator.speed = m_speed;        m_curAnimName = string.Empty;        m_clipEndAutoSetNull = true;        clipEndCallbacks = new Dictionary<string, Action<string>>();    }    public void Play(string animName, float speed = 1)    {        if (string.IsNullOrEmpty(animName))            return;        Speed = speed;        m_animator.Play(animName, 0, 0);        m_curAnimName = animName;    }    public void Stop()    {        Speed = 0f;    }    ///     /// 将动画定格在这一瞬间    ///     ///     ///     public void SetAnimStartPose(string animName, float progress)    {        if (string.IsNullOrEmpty(animName))            return;        m_animator.Play(animName, 0, progress);        m_animator.Update(0);        Speed = 0f;    }    ///     /// 设置动画结束的回调    ///     ///     ///     public void SetAnimEndCallback(string animName, Action<string> endCallback)    {        if (string.IsNullOrEmpty(animName))            return;        if (clipEndCallbacks.ContainsKey(animName))        {            clipEndCallbacks[animName] = endCallback;        }        else        {            clipEndCallbacks.Add(animName, endCallback);        }    }    private void Update()    {        if (!string.IsNullOrEmpty(m_curAnimName))        {            AnimatorStateInfo info = m_animator.GetCurrentAnimatorStateInfo(0);            if (info.IsName(m_curAnimName) && info.normalizedTime >= 1f)//如果动画为loop模式,则此代码会有bug            {                Action<string> callback = null;                clipEndCallbacks.TryGetValue(m_curAnimName, out callback);                callback?.Invoke(m_curAnimName);                if (m_clipEndAutoSetNull)                {                    clipEndCallbacks[m_curAnimName] = null;                }            }        }    }}

测试:

using System.Collections;using System.Collections.Generic;using UnityEngine;public class Test : MonoBehaviour{    public AnimCtrler ctrler;    public void Update()    {        if (Input.GetMouseButtonDown(0))        {            ctrler.ClipEndAutoSetNull = true;            ctrler.SetAnimEndCallback("Move", (name) => Debug.Log(name));            ctrler.Play("Move");        }        else if (Input.GetMouseButtonDown(1))        {            ctrler.SetAnimStartPose("Exit", 1);        }    }}

总结:

在监听动画完成条件这里会有一个bug,如果Clip的模式为loop,normalizedTime是会一直增长的,所以使用AnimCtrler脚本,动画片段都不应该是loop模式(靠normalizedTime判断是否播放完,对这个问题是无解的,我在考虑是否用我以前写的Timer来监听)。当然如果你使用Play重复播,监听还是有效的。Animator的参数意义可以参考【Unity3D】Animation 和 Animator 动画重置到起始帧的方法_机灵鹤的博客-CSDN博客_animator.update