using System; using UnityEngine; namespace AibisDream.FrameAnimation { internal readonly struct FrameAnimationSessionEvaluation { public static FrameAnimationSessionEvaluation Running => new FrameAnimationSessionEvaluation( false, FrameClipEndBehavior.HoldLastFrame, FrameAnimationPlaybackError.None); public bool IsCompleted { get; } public FrameClipEndBehavior EndBehavior { get; } public FrameAnimationPlaybackError Error { get; } public bool IsFailed => Error.Code != FrameAnimationPlaybackErrorCode.None; private FrameAnimationSessionEvaluation( bool isCompleted, FrameClipEndBehavior endBehavior, FrameAnimationPlaybackError error) { IsCompleted = isCompleted; EndBehavior = endBehavior; Error = error; } public static FrameAnimationSessionEvaluation Completed(FrameClipEndBehavior endBehavior) { return new FrameAnimationSessionEvaluation(true, endBehavior, FrameAnimationPlaybackError.None); } public static FrameAnimationSessionEvaluation Failed( FrameAnimationPlaybackErrorCode code, string message) { return new FrameAnimationSessionEvaluation( false, FrameClipEndBehavior.HoldLastFrame, new FrameAnimationPlaybackError(code, message)); } } internal sealed class FrameAnimationPlaybackSession { private const double Epsilon = 0.000000001d; private readonly ResolvedPlaybackPlan plan; private int stepIndex; private int frameIndex; private double frameElapsedSeconds; private bool hasStarted; private bool hasCompleted; public string PlayableId => plan.PlayableId; public string CurrentClipId => CurrentStep?.Clip != null ? CurrentStep.Clip.Id : string.Empty; public string CurrentNodeId => plan.IsFlow && CurrentStep != null ? CurrentStep.NodeId : string.Empty; public int CurrentFrameIndex => hasStarted && !hasCompleted ? frameIndex : -1; public FrameClip CurrentClip => CurrentStep?.Clip; private ResolvedPlaybackStep CurrentStep => stepIndex >= 0 && stepIndex < plan.Steps.Count ? plan.Steps[stepIndex] : null; internal FrameAnimationPlaybackSession(ResolvedPlaybackPlan plan) { this.plan = plan ?? throw new ArgumentNullException(nameof(plan)); } public void Start(Action applySprite) { if (hasStarted) { return; } hasStarted = true; stepIndex = 0; frameIndex = 0; frameElapsedSeconds = 0d; ApplyCurrentFrame(applySprite); } public FrameAnimationSessionEvaluation Evaluate( double deltaTimeSeconds, float playerSpeed, Action applySprite) { if (!hasStarted || hasCompleted || deltaTimeSeconds <= 0d) { return FrameAnimationSessionEvaluation.Running; } var remainingRealSeconds = deltaTimeSeconds; while (remainingRealSeconds > Epsilon) { var step = CurrentStep; if (step?.Clip == null || frameIndex < 0 || frameIndex >= step.Clip.FrameCount) { hasCompleted = true; return FrameAnimationSessionEvaluation.Failed( FrameAnimationPlaybackErrorCode.InvalidPlayableData, "播放期间 Clip 或 Frame 数据失效。"); } var effectiveSpeed = playerSpeed * step.PlaybackSpeed; if (!FrameAnimationValueUtility.IsValidSpeed(effectiveSpeed)) { hasCompleted = true; return FrameAnimationSessionEvaluation.Failed( FrameAnimationPlaybackErrorCode.InvalidSpeed, "播放期间有效速度变为无效值。"); } if (effectiveSpeed == 0f) { return FrameAnimationSessionEvaluation.Running; } if (IsTerminalLoop(step) && frameIndex == 0 && frameElapsedSeconds <= Epsilon) { var cycleRealDuration = GetClipDurationSeconds(step.Clip) / effectiveSpeed; if (cycleRealDuration > Epsilon && remainingRealSeconds >= cycleRealDuration) { remainingRealSeconds %= cycleRealDuration; if (remainingRealSeconds <= Epsilon) { return FrameAnimationSessionEvaluation.Running; } } } var frame = step.Clip.Frames[frameIndex]; if (frame == null || frame.DurationMs <= 0) { hasCompleted = true; return FrameAnimationSessionEvaluation.Failed( FrameAnimationPlaybackErrorCode.InvalidPlayableData, "播放期间 Frame 数据失效或 durationMs 不再有效。"); } var frameDurationSeconds = frame.DurationMs / 1000d; var remainingAnimationSeconds = Math.Max(0d, frameDurationSeconds - frameElapsedSeconds); var realSecondsToBoundary = remainingAnimationSeconds / effectiveSpeed; if (remainingRealSeconds + Epsilon < realSecondsToBoundary) { frameElapsedSeconds += remainingRealSeconds * effectiveSpeed; return FrameAnimationSessionEvaluation.Running; } remainingRealSeconds = Math.Max(0d, remainingRealSeconds - realSecondsToBoundary); frameElapsedSeconds = 0d; var completion = AdvanceFrameOrStep(applySprite); if (completion.IsCompleted) { return completion; } } return FrameAnimationSessionEvaluation.Running; } private FrameAnimationSessionEvaluation AdvanceFrameOrStep(Action applySprite) { var step = CurrentStep; frameIndex++; if (frameIndex < step.Clip.FrameCount) { ApplyCurrentFrame(applySprite); return FrameAnimationSessionEvaluation.Running; } var isTerminalStep = stepIndex == plan.Steps.Count - 1; if (!isTerminalStep) { stepIndex++; frameIndex = 0; ApplyCurrentFrame(applySprite); return FrameAnimationSessionEvaluation.Running; } if (step.TerminalEndBehavior == FrameClipEndBehavior.Loop) { frameIndex = 0; ApplyCurrentFrame(applySprite); return FrameAnimationSessionEvaluation.Running; } frameIndex = step.Clip.FrameCount - 1; hasCompleted = true; return FrameAnimationSessionEvaluation.Completed(step.TerminalEndBehavior); } private bool IsTerminalLoop(ResolvedPlaybackStep step) { return stepIndex == plan.Steps.Count - 1 && step.TerminalEndBehavior == FrameClipEndBehavior.Loop; } private static double GetClipDurationSeconds(FrameClip clip) { return clip.TotalDurationMs / 1000d; } private void ApplyCurrentFrame(Action applySprite) { var step = CurrentStep; if (step?.Clip == null || frameIndex < 0 || frameIndex >= step.Clip.FrameCount) { return; } applySprite?.Invoke(step.Clip.Frames[frameIndex]?.Sprite); } } }