Unity获取Animator动画播放完成事件

整理了一些在日常经验中处理动画播放完成事件的方法

方法:

1.Dotween配合异步实现

2.状态机计时方法实现

3.原生动画行为方法实现

方法一:Dotween异步方法

csharp 复制代码
using UnityEngine;
using System.Threading.Tasks;
using DG.Tweening;

public class PlayerAnimAsync : MonoBehaviour
{
    private Animator animator;
    private bool isAnimPlaying = false;

    void Start()
    {
        animator = GetComponent<Animator>();
    }

    void Update()
    {
        animator = GetComponent<Animator>();

        // 开始动画
        if (!isAnimPlaying)
        {
            StartAttack();
        }
    }

    private async void StartAttack()
    {
        isAnimPlaying = true;
        await AnimationFinish("Attack", 0f);   //等待Attack播放完
        Debug.Log("Attack播放完了,可以执行Idle");
        animator.Play("Idle");
        isAnimPlaying = false;
    }

    //AnimationFinish 异步播放动画
    public async Task AnimationFinish(string animName, float extreTime = 0f)
    {
        await DOTween.Sequence()
            .AppendCallback(() => animator.Play(animName))
            .AppendInterval(GetAnimationClipLength(animName) + extreTime)
            .AsyncWaitForCompletion();
    }

    //GetAnimationClipLength 获取动画片段时长
    private float GetAnimationClipLength(string animName)
    {
        RuntimeAnimatorController ac = animator.runtimeAnimatorController;
        foreach (AnimationClip clip in ac.animationClips)
        {
            if (clip.name == animName)
            {
                return clip.length;
            }
        }
        return 0f;
    }
}

方法二:状态机计时方法

csharp 复制代码
using UnityEngine;
public class PlayerAnimFSM : MonoBehaviour
{
    private enum AnimationState {Idle, Attack}
    private AnimationState currentState;
    private Animator animator;
    private float waitAnimTime = 0f;    //动画计时器

    void Start()
    {
        animator = GetComponent<Animator>();
    }

    void Update()
    {
        CheckState();
        currentState = currentState switch
        {
            AnimationState.Idle => IdleState(),
            AnimationState.Attack => AttackState(),
            _ => currentState
        };
    }

    private AnimationState IdleState()
    {
        animator.Play("Idle");
        return AnimationState.Idle;
    }

    private AnimationState AttackState()
    {
        //播放动画
        animator.Play("Attack");
        waitAnimTime += Time.deltaTime;
        if (waitAnimTime >= animator.GetCurrentAnimatorStateInfo(0).length)
        {
            //当动画记录时间大于当前正在播放动画的时间时
            //todo:这里有一个BUG,在animator.Play()的动画需要在下一帧animator.GetCurrentAnimatorStateInfo(0).length才能获取到正确的时间
            //在这里默认动画长度都会大于1帧所以没太大的问题
            //正确的做法是参考方法一种的GetAnimationClipLength来获取动画时间
            waitAnimTime = 0;   //重置动画时间
            Debug.Log("Attack播放完了,可以执行Idle");
            return AnimationState.Idle; //转换Idle状态
        }
        return AnimationState.Attack; //维持攻击状态
    }

    private void CheckState()    //检测状态转换
    {
        if (currentState == AnimationState.Attack)  //维持攻击动画不被打断
            return;
        if (Input.GetKeyDown(KeyCode.Space))
        {
            currentState = AnimationState.Attack;
            return;
        }
        currentState = AnimationState.Idle;
    }
}

方法三:原生动画行为方法实现

这里需要用到两个脚本PlayerAnimSM和AttackFinish来实现,此处isAnimPlaying借助原生动画行为来复原

csharp 复制代码
using UnityEngine;

public class PlayerAnimSM : MonoBehaviour
{
    private Animator animator;
    public bool isAnimPlaying = false;

    void Start()
    {
        animator = GetComponent<Animator>();
    }

    void Update()
    {
        animator = GetComponent<Animator>();

        // 开始动画
        if (!isAnimPlaying)
        {
            StartAttack();
        }
    }

    private void StartAttack()
    {
        isAnimPlaying = true;
        animator.Play("Attack");
    }
}

AttackFinish脚本借助界面创建步骤如下:

1.在动画器中点击需要传递动画完成事件的动画,点击右下角的Add Behaviour(添加行为),可以添加Unity预制的脚本

2.使用这个方法需要有动画过渡的方式(此处为AnyState到Idle),供后续代码中的OnStateExit使用(举例:如果希望有攻击结束到闲置动画的过渡,就需要从攻击动画连线到闲置动画,重点!!!一定要有退出时间,设置0s也没事,但一定要勾选,这里我就使用AnyState过渡过去了,使用AnyState时也一定要勾选退出时间

创建名为AttackFinish 的脚本(这里Unity叫行为)

双击点开这个脚本

csharp 复制代码
using UnityEngine;

public class AttackFinish : StateMachineBehaviour
{
    // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
    //override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    //{
    //}

    // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
    //override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    //{

    //}

    //OnStateExit is called when a transition ends and the state machine finishes evaluating this state
    override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        //在此处将isAnimPlaying重置回false
        animator.GetComponent<PlayerAnimSM>().isAnimPlaying = false;
        Debug.Log("Attack播放完了");
        //播放动画结束后的默认动画,我这里设置为idle你可以设置为任意动画但是一定要有过渡,从Attack到Idle的过渡
        animator.Play("Idle");
    }

    // OnStateMove is called right after Animator.OnAnimatorMove()
    //override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    //{
    //    // Implement code that processes and affects root motion
    //}

    // OnStateIK is called right after Animator.OnAnimatorIK()
    //override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    //{
    //    // Implement code that sets up animation IK (inverse kinematics)
    //}
}

后谈:还有一些诸如动画帧事件的方法没有收录在内。作者总感觉无论哪一个方法都不是特别合适或者顺手,当然无论是用原生还是自己去写,寻找适合自己项目的方法才是最好的。所以作者也会在今后的开发道路上继续学习。

相关推荐
躺下睡觉~4 小时前
Unity-Transform类-父子关系
java·unity·游戏引擎
躺下睡觉~4 小时前
Unity-Transform类-缩放和看向
unity·游戏引擎
君莫愁。6 小时前
【Unity】检测鼠标点击位置是否有2D对象
unity·c#·游戏引擎
咩咩觉主7 小时前
Unity实战案例全解析:PVZ 植物卡片状态分析
unity·c#·游戏引擎
蓝裕安10 小时前
伪工厂模式制造敌人
开发语言·unity·游戏引擎
谢泽浩14 小时前
Unity 给模型贴上照片
unity·游戏引擎
z2014z14 小时前
Unity Resource System 优化笔记
unity·游戏引擎
王维志14 小时前
Unity 高亮插件HighlightPlus介绍
unity·游戏引擎
zaizai100715 小时前
我的demo保卫萝卜中的技术要点
unity
菌菌巧乐兹17 小时前
Unity 百度AI实现无绿幕拍照抠像功能(详解版)
人工智能·百度·unity