using System; using System.ComponentModel; using UnityEngine; using UnityEngine.Playables; using UnityEngine.Timeline; namespace AibisDream.FrameAnimation.Timeline { [Serializable] [DisplayName("Frame Animation Clip")] public sealed class FrameAnimationTimelineClip : PlayableAsset, ITimelineClipAsset { private const double FallbackDurationSeconds = 1d; [SerializeField, Tooltip("要由 Timeline 播放的独立 FrameClip 资产。")] private FrameClip frameClip; [SerializeField, HideInInspector] private FrameClip synchronizedDurationSource; [SerializeField, HideInInspector] private bool hasSynchronizedDuration; public FrameClip FrameClip => frameClip; public override double duration => GetEffectiveDuration(frameClip); public ClipCaps clipCaps { get { var caps = ClipCaps.ClipIn | ClipCaps.SpeedMultiplier; if (frameClip != null && frameClip.DefaultEndBehavior == FrameClipEndBehavior.Loop) { caps |= ClipCaps.Looping; } return caps; } } public override Playable CreatePlayable(PlayableGraph graph, GameObject owner) { var playable = ScriptPlayable.Create(graph); playable.GetBehaviour().FrameClip = frameClip; return playable; } internal void SetFrameClipForTests(FrameClip value) { frameClip = value; } internal bool TrySynchronizeSourceDuration(out double sourceDuration) { sourceDuration = duration; if (hasSynchronizedDuration && synchronizedDurationSource == frameClip) { return false; } synchronizedDurationSource = frameClip; hasSynchronizedDuration = true; return true; } internal static double GetEffectiveDuration(FrameClip value) { if (value == null || value.TotalDurationMs <= 0 || value.Speed <= 0f) { return FallbackDurationSeconds; } return value.TotalDurationMs / 1000d / value.Speed; } } internal sealed class FrameAnimationTimelineBehaviour : PlayableBehaviour { internal FrameClip FrameClip { get; set; } } }