using System; using System.Threading; using UnityEngine; using UnityEngine.UI; namespace AibisDream.FrameAnimation { [DisallowMultipleComponent] public sealed class FrameAnimationPlayer : MonoBehaviour { [SerializeField] private FrameAnimationGraph graph; [SerializeField] private bool playOnEnable; [SerializeField] private float speed = 1f; private static long nextRequestId; private IFrameAnimationTarget target; private FrameAnimationPlaybackSession session; private FrameAnimationPlaybackHandle activeHandle; private FrameAnimationPlaybackState state = FrameAnimationPlaybackState.Stopped; private bool _skipInitialPlayOnEnable; private bool hasStartedPlayback; public FrameAnimationGraph Graph => graph; public bool PlayOnEnable => playOnEnable; public FrameAnimationPlaybackState State => state; public string CurrentPlayableId => session?.PlayableId ?? string.Empty; public string CurrentClipId => session?.CurrentClipId ?? string.Empty; public string CurrentNodeId => session?.CurrentNodeId ?? string.Empty; public int CurrentFrameIndex => session?.CurrentFrameIndex ?? -1; public float Speed => speed; private void Awake() { _skipInitialPlayOnEnable = playOnEnable; if (TryCreateTarget(out var resolvedTarget, out _)) { target = resolvedTarget; } } private void OnEnable() { if (!playOnEnable) { return; } if (_skipInitialPlayOnEnable) { _skipInitialPlayOnEnable = false; return; } TryAutoPlay(); } private void Start() { if (!playOnEnable) { return; } TryAutoPlay(); } private void TryAutoPlay() { var handle = Play(); if (handle.IsCompleted && handle.Result.Reason == FrameAnimationCompletionReason.Failed) { Debug.LogError($"FrameAnimationPlayer 自动播放失败:{handle.Result.Error}", this); } } private void Update() { if (state != FrameAnimationPlaybackState.Playing || session == null) { return; } if (target == null || !target.IsValid) { FailActive(FrameAnimationPlaybackErrorCode.TargetMissing, "播放期间显示目标失效。"); return; } if (graph == null || session.CurrentClip == null) { FailActive(FrameAnimationPlaybackErrorCode.InvalidPlayableData, "播放期间 Graph 或 Clip 数据失效。"); return; } if (!FrameAnimationValueUtility.IsValidSpeed(speed)) { FailActive(FrameAnimationPlaybackErrorCode.InvalidSpeed, "播放期间 Player speed 变为无效值。"); return; } var evaluation = session.Evaluate(Time.deltaTime, speed, ApplySprite); if (evaluation.IsFailed) { FailActive(evaluation.Error.Code, evaluation.Error.Message); } else if (evaluation.IsCompleted) { CompleteNaturally(evaluation.EndBehavior); } } private void OnDisable() { TerminateActive(FrameAnimationCompletionReason.Stopped, FrameAnimationPlaybackError.None); } private void OnDestroy() { TerminateActive(FrameAnimationCompletionReason.Stopped, FrameAnimationPlaybackError.None); } public FrameAnimationPlaybackHandle Play() { var playableId = graph?.Settings?.DefaultPlayableId ?? string.Empty; return PlayInternal(playableId, default); } public FrameAnimationPlaybackHandle Play(string playableId) { return PlayInternal(playableId, default); } public FrameAnimationPlaybackHandle Play(string playableId, FrameAnimationPlayOptions options) { return PlayInternal(playableId, options); } /// /// 等待当前播放请求完成首轮。非循环请求等待自然结束;终点为 Loop 的请求在 /// 第一次回绕后结束等待,但播放请求本身保持活动并继续循环。 /// public CustomYieldInstruction WaitForFirstPass(FrameAnimationPlaybackHandle playbackHandle) { if (playbackHandle == null) { throw new ArgumentNullException(nameof(playbackHandle)); } return new FirstPassYieldInstruction(this, playbackHandle); } public bool TryBindGraph( FrameAnimationGraph value, out FrameAnimationPlaybackError error) { if (value == null) { error = new FrameAnimationPlaybackError( FrameAnimationPlaybackErrorCode.GraphMissing, "不能为 FrameAnimationPlayer 绑定空 Graph。"); return false; } if (hasStartedPlayback || state != FrameAnimationPlaybackState.Stopped || session != null || (activeHandle != null && !activeHandle.IsCompleted)) { error = new FrameAnimationPlaybackError( FrameAnimationPlaybackErrorCode.GraphBindingNotAllowed, "FrameAnimationPlayer 开始播放后不允许绑定或替换 Graph。"); return false; } if (graph != null && graph != value) { error = new FrameAnimationPlaybackError( FrameAnimationPlaybackErrorCode.GraphBindingNotAllowed, "FrameAnimationPlayer 已绑定其他 Graph。"); return false; } graph = value; error = FrameAnimationPlaybackError.None; return true; } /// /// 非循环 playable 立即应用最后一帧及结束行为;Loop 从头重新播放。 /// public FrameAnimationPlaybackHandle RestoreTerminalState(string playableId) { var requestId = Interlocked.Increment(ref nextRequestId); var handle = new FrameAnimationPlaybackHandle(requestId); if (!isActiveAndEnabled) { return CompleteFailed( handle, playableId, FrameAnimationPlaybackErrorCode.PlayerNotReady, "FrameAnimationPlayer 未启用或 GameObject 未激活。"); } if (graph == null) { return CompleteFailed( handle, playableId, FrameAnimationPlaybackErrorCode.GraphMissing, "FrameAnimationPlayer 没有绑定 FrameAnimationGraph。"); } if (!FrameAnimationValueUtility.IsValidSpeed(speed)) { return CompleteFailed( handle, playableId, FrameAnimationPlaybackErrorCode.InvalidSpeed, "FrameAnimationPlayer speed 无效。"); } if (!TryCreateTarget(out var resolvedTarget, out var targetError)) { return CompleteFailed(handle, playableId, targetError.Code, targetError.Message); } if (!FrameAnimationResolver.TryResolve( graph, playableId, default, out var plan, out var resolveError)) { return CompleteFailed(handle, playableId, resolveError.Code, resolveError.Message); } if (activeHandle != null && !activeHandle.IsCompleted) { CompleteHandle( activeHandle, CurrentPlayableId, FrameAnimationCompletionReason.Replaced, FrameAnimationPlaybackError.None); } hasStartedPlayback = true; target = resolvedTarget; target.Enabled = true; var terminalStep = plan.Steps[plan.Steps.Count - 1]; if (terminalStep.TerminalEndBehavior == FrameClipEndBehavior.Loop) { session = new FrameAnimationPlaybackSession(plan); activeHandle = handle; state = FrameAnimationPlaybackState.Playing; session.Start(ApplySprite); return handle; } var lastFrameIndex = terminalStep.Clip.FrameCount - 1; ApplySprite(terminalStep.Clip.Frames[lastFrameIndex]?.Sprite); ApplyEndBehavior(terminalStep.TerminalEndBehavior); session = null; activeHandle = null; state = FrameAnimationPlaybackState.Stopped; CompleteHandle( handle, playableId, FrameAnimationCompletionReason.Completed, FrameAnimationPlaybackError.None); return handle; } public void Stop(FrameAnimationStopMode mode = FrameAnimationStopMode.HoldCurrentFrame) { TerminateActive(FrameAnimationCompletionReason.Stopped, FrameAnimationPlaybackError.None); if (target == null || !target.IsValid) { if (!TryCreateTarget(out var resolvedTarget, out _)) { return; } target = resolvedTarget; } ApplyStopMode(mode); } public void Pause() { if (state == FrameAnimationPlaybackState.Playing && session != null) { state = FrameAnimationPlaybackState.Paused; } } public void Resume() { if (state == FrameAnimationPlaybackState.Paused && session != null) { state = FrameAnimationPlaybackState.Playing; } } public void SetSpeed(float value) { if (!FrameAnimationValueUtility.IsValidSpeed(value)) { Debug.LogError($"FrameAnimationPlayer speed 必须是有限且不小于 0 的数值,收到:{value}", this); return; } speed = value; } internal void ConfigureForAuthoring(FrameAnimationGraph value, bool shouldPlayOnEnable, float playerSpeed) { graph = value; playOnEnable = shouldPlayOnEnable; speed = playerSpeed; hasStartedPlayback = false; } internal void EvaluateForTests(double deltaTimeSeconds) { if (state != FrameAnimationPlaybackState.Playing || session == null) { return; } var evaluation = session.Evaluate(deltaTimeSeconds, speed, ApplySprite); if (evaluation.IsFailed) { FailActive(evaluation.Error.Code, evaluation.Error.Message); } else if (evaluation.IsCompleted) { CompleteNaturally(evaluation.EndBehavior); } } private FrameAnimationPlaybackHandle PlayInternal( string playableId, FrameAnimationPlayOptions options) { var requestId = Interlocked.Increment(ref nextRequestId); var handle = new FrameAnimationPlaybackHandle(requestId); if (!isActiveAndEnabled) { return CompleteFailed( handle, playableId, FrameAnimationPlaybackErrorCode.PlayerNotReady, "FrameAnimationPlayer 未启用或 GameObject 未激活。"); } if (graph == null) { return CompleteFailed( handle, playableId, FrameAnimationPlaybackErrorCode.GraphMissing, "FrameAnimationPlayer 没有绑定 FrameAnimationGraph。"); } if (!FrameAnimationValueUtility.IsValidSpeed(speed)) { return CompleteFailed( handle, playableId, FrameAnimationPlaybackErrorCode.InvalidSpeed, "FrameAnimationPlayer speed 无效。"); } if (!TryCreateTarget(out var resolvedTarget, out var targetError)) { return CompleteFailed(handle, playableId, targetError.Code, targetError.Message); } if (!FrameAnimationResolver.TryResolve(graph, playableId, options, out var plan, out var resolveError)) { return CompleteFailed(handle, playableId, resolveError.Code, resolveError.Message); } hasStartedPlayback = true; if (activeHandle != null && !activeHandle.IsCompleted) { CompleteHandle(activeHandle, CurrentPlayableId, FrameAnimationCompletionReason.Replaced, FrameAnimationPlaybackError.None); } session = new FrameAnimationPlaybackSession(plan); activeHandle = handle; state = FrameAnimationPlaybackState.Playing; target = resolvedTarget; target.Enabled = true; session.Start(ApplySprite); return handle; } private bool TryCreateTarget( out IFrameAnimationTarget resolvedTarget, out FrameAnimationPlaybackError error) { var spriteRenderer = GetComponent(); var image = GetComponent(); if (spriteRenderer != null && image != null) { resolvedTarget = null; error = new FrameAnimationPlaybackError( FrameAnimationPlaybackErrorCode.TargetConflict, "FrameAnimationPlayer 所在对象不能同时包含 SpriteRenderer 和 Image。"); return false; } if (spriteRenderer == null && image == null) { resolvedTarget = null; error = new FrameAnimationPlaybackError( FrameAnimationPlaybackErrorCode.TargetMissing, "FrameAnimationPlayer 所在对象缺少 SpriteRenderer 或 Image。"); return false; } resolvedTarget = spriteRenderer != null ? new SpriteRendererFrameAnimationTarget(spriteRenderer) : new ImageFrameAnimationTarget(image); error = FrameAnimationPlaybackError.None; return true; } private FrameAnimationPlaybackHandle CompleteFailed( FrameAnimationPlaybackHandle handle, string playableId, FrameAnimationPlaybackErrorCode code, string message) { CompleteHandle( handle, playableId, FrameAnimationCompletionReason.Failed, new FrameAnimationPlaybackError(code, message)); return handle; } private void CompleteNaturally(FrameClipEndBehavior endBehavior) { var handle = activeHandle; var playableId = CurrentPlayableId; ApplyEndBehavior(endBehavior); session = null; activeHandle = null; state = FrameAnimationPlaybackState.Stopped; CompleteHandle( handle, playableId, FrameAnimationCompletionReason.Completed, FrameAnimationPlaybackError.None); } private void FailActive(FrameAnimationPlaybackErrorCode code, string message) { var handle = activeHandle; var playableId = CurrentPlayableId; session = null; activeHandle = null; state = FrameAnimationPlaybackState.Stopped; CompleteHandle( handle, playableId, FrameAnimationCompletionReason.Failed, new FrameAnimationPlaybackError(code, message)); } private void TerminateActive( FrameAnimationCompletionReason reason, FrameAnimationPlaybackError error) { if (activeHandle == null || activeHandle.IsCompleted) { session = null; activeHandle = null; state = FrameAnimationPlaybackState.Stopped; return; } var handle = activeHandle; var playableId = CurrentPlayableId; session = null; activeHandle = null; state = FrameAnimationPlaybackState.Stopped; CompleteHandle(handle, playableId, reason, error); } private static void CompleteHandle( FrameAnimationPlaybackHandle handle, string playableId, FrameAnimationCompletionReason reason, FrameAnimationPlaybackError error) { handle?.TryComplete(new FrameAnimationPlaybackResult(handle.RequestId, playableId, reason, error)); } private void ApplySprite(Sprite sprite) { if (target != null && target.IsValid) { target.Sprite = sprite; } } private bool ShouldWaitForFirstPass(FrameAnimationPlaybackHandle playbackHandle) { if (playbackHandle.IsCompleted || activeHandle != playbackHandle || session == null) { return false; } return !session.HasCompletedFirstTerminalLoopCycle; } private sealed class FirstPassYieldInstruction : CustomYieldInstruction { private readonly FrameAnimationPlayer player; private readonly FrameAnimationPlaybackHandle playbackHandle; public FirstPassYieldInstruction( FrameAnimationPlayer player, FrameAnimationPlaybackHandle playbackHandle) { this.player = player; this.playbackHandle = playbackHandle; } public override bool keepWaiting => player != null && player.ShouldWaitForFirstPass(playbackHandle); } private void ApplyEndBehavior(FrameClipEndBehavior endBehavior) { switch (endBehavior) { case FrameClipEndBehavior.HoldLastFrame: break; case FrameClipEndBehavior.Clear: if (target != null && target.IsValid) { target.Enabled = true; target.Sprite = null; } break; case FrameClipEndBehavior.HideTarget: if (target != null && target.IsValid) { target.Enabled = false; } break; } } private void ApplyStopMode(FrameAnimationStopMode mode) { switch (mode) { case FrameAnimationStopMode.HoldCurrentFrame: break; case FrameAnimationStopMode.Clear: target.Enabled = true; target.Sprite = null; break; case FrameAnimationStopMode.HideTarget: target.Enabled = false; break; } } } }