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)
{
if (GetComponent() != null)
{
resolvedTarget = null;
error = new FrameAnimationPlaybackError(
FrameAnimationPlaybackErrorCode.TargetConflict,
"同一 GameObject 不能同时包含 FrameAnimationPlayer 和 FrameClipPlayer。");
return false;
}
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;
}
}
}
}