UI 序列帧动画控制器

0 阅读2分钟
using System.Collections;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using Cysharp.Threading.Tasks;

/// <summary>
/// UI 序列帧动画控制器
/// 主要用于:
— 角色头像动画
— NPC 说话动画
— UI 动画循环
/// 
/// 功能:
/// Idle 待机动画
/// Talk 说话动画
/// 支持循环播放
/// 支持正反向循环
/// 支持播放完成回调
/// 支持 UniTask 异步等待动画结束
/// </summary>
public class PictureSequenceFrames : MonoBehaviour
{
    /// <summary>
    /// 待机动画帧(Idle)
    /// 一般是角色眨眼、轻微动作
    /// </summary>
    public List<Sprite> idleFrames;

    /// <summary>
    /// 说话动画帧
    /// 一般用于对话系统口型动画
    /// </summary>
    public List<Sprite> TalkFrames;

    /// <summary>
    /// 动画帧率
    /// 例如:
    /// 12 = 每秒12帧
    /// </summary>
    public float frameRate = 12f;

    /// <summary>
    /// 是否循环播放
    /// true:循环(正放 + 反放)
    /// false:只播放一次
    /// </summary>
    public bool loop;

    /// <summary>
    /// 是否启用 Idle 动画
    /// 当物体启用时自动播放
    /// </summary>
    public bool enableIdle = true;

    /// <summary>
    /// 播放完成回调
    /// 只在非循环模式下触发
    /// </summary>
    [Tooltip("当序列帧播放完成后触发的事件")]
    public UnityEvent OnPlaybackComplete;

    /// <summary>
    /// 当前 UI Image 组件
    /// 用于显示 Sprite
    /// </summary>
    private Image image;

    /// <summary>
    /// 当前播放到的帧索引
    /// </summary>
    private int currentFrameIndex = 0;

    /// <summary>
    /// 是否正在播放动画
    /// 防止重复启动 Coroutine
    /// </summary>
    private bool isPlaying = false;

    /// <summary>
    /// 是否反向播放
    /// 用于循环模式的往返动画
    /// </summary>
    private bool reversePlayback = false;

    /// <summary>
    /// 当前是否处于说话状态
    /// true = 播放 TalkFrames
    /// false = 播放 IdleFrames
    /// </summary>
    private bool isTalking = false;

    /// <summary>
    /// 当物体被启用时执行
    /// 作用:
    /// 初始化组件
    /// 自动播放 Idle 动画
    /// </summary>
    void OnEnable()
    {
        image = GetComponent<Image>();

        // 重置播放状态
        isPlaying = false;

        // 如果开启 Idle 且有帧,则自动播放
        if (idleFrames != null && idleFrames.Count > 0 && enableIdle)
        {
            Play();
        }
    }

    /// <summary>
    /// 播放 Idle 动画
    /// </summary>
    public void Play()
    {
        if (!isPlaying && idleFrames != null && idleFrames.Count > 0)
        {
            isPlaying = true;
            currentFrameIndex = 0;
            reversePlayback = false;

            StartCoroutine(PlayAnimation());
        }
    }

    /// <summary>
    /// 开始说话动画
    /// 在对话系统中调用
    /// </summary>
    [Button]
    public void StartTalking()
    {
        if (TalkFrames != null && TalkFrames.Count > 0)
        {
            isTalking = true;

            // 如果当前没有播放动画,则启动
            if (!isPlaying)
            {
                isPlaying = true;
                currentFrameIndex = 0;
                reversePlayback = false;

                StartCoroutine(PlayAnimation());
            }
        }
    }

    /// <summary>
    /// 开始说话动画(异步版本)
    /// 可以 await 等待动画播放结束
    /// </summary>
    public async UniTask StartTalkingAsync()
    {
        isTalking = true;

        if (!isPlaying)
        {
            isPlaying = true;
            currentFrameIndex = 0;
            reversePlayback = false;

            StartCoroutine(PlayAnimation());
        }

        // 等待动画播放结束
        await UniTask.WaitUntil(() => !isPlaying);
    }

    /// <summary>
    /// 停止说话动画
    /// 回到 Idle
    /// </summary>
    [Button]
    public void StopTalking()
    {
        isTalking = false;
        currentFrameIndex = 0;
        reversePlayback = false;

        // 如果 Idle 未开启,则重新播放
        if (!enableIdle)
        {
            Play();
        }
    }

    /// <summary>
    /// 停止说话动画(异步版本)
    /// </summary>
    public async UniTask StopTalkingAsync()
    {
        isTalking = false;
        currentFrameIndex = 0;
        reversePlayback = false;

        if (!enableIdle)
        {
            Play();
        }

        await UniTask.WaitUntil(() => !isPlaying);
    }

    /// <summary>
    /// 核心动画播放协程
    /// 控制序列帧切换
    /// </summary>
    public IEnumerator PlayAnimation()
    {
        while (isPlaying)
        {
            // 根据当前状态决定播放哪组动画
            List<Sprite> currentFrames = isTalking ? TalkFrames : idleFrames;

            // 如果帧不存在,则停止
            if (currentFrames == null || currentFrames.Count == 0)
            {
                isPlaying = false;
                yield break;
            }

            // 设置当前帧
            image.sprite = currentFrames[currentFrameIndex];

            // 等待下一帧时间
            yield return new WaitForSeconds(1f / frameRate);

            if (loop)
            {
                // 循环模式(往返播放)
                if (!reversePlayback)
                {
                    currentFrameIndex++;

                    if (currentFrameIndex >= currentFrames.Count)
                    {
                        // 到最后一帧开始反向
                        currentFrameIndex = Mathf.Max(0, currentFrames.Count - 2);
                        reversePlayback = true;
                    }
                }
                else
                {
                    currentFrameIndex--;

                    if (currentFrameIndex < 0)
                    {
                        // 到第一帧再正向播放
                        currentFrameIndex = Mathf.Min(1, currentFrames.Count - 1);
                        reversePlayback = false;
                    }
                }
            }
            else
            {
                // 单次播放模式
                currentFrameIndex++;

                if (currentFrameIndex >= currentFrames.Count)
                {
                    isPlaying = false;

                    // 触发播放完成事件
                    OnPlaybackComplete?.Invoke();
                }
            }
        }
    }
}