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();
}
}
}
}
}