前言:我们无法监听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