using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using UnityEngine; using UnityEngine.Playables; using UnityEngine.Timeline; using UnityEngine.UI; namespace AibisDream.FrameAnimation.Timeline { [DisplayName("Frame Animation Track")] [TrackColor(0.35f, 0.72f, 0.95f)] [TrackBindingType(typeof(FrameClipPlayer))] [TrackClipType(typeof(FrameAnimationTimelineClip))] public sealed class FrameAnimationTrack : TrackAsset { public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount) { var playable = ScriptPlayable.Create(graph, inputCount); playable.GetBehaviour().SetClips( GetClips() .Select((clip, index) => new FrameAnimationTimelineClipInfo(clip, index)) .ToArray()); return playable; } public override void GatherProperties(PlayableDirector director, IPropertyCollector driver) { var player = director != null ? director.GetGenericBinding(this) as FrameClipPlayer : null; if (player != null) { var spriteRenderer = player.GetComponent(); if (spriteRenderer != null) { driver.AddFromName(player.gameObject, "m_Sprite"); driver.AddFromName(player.gameObject, "m_Enabled"); } var image = player.GetComponent(); if (image != null) { driver.AddFromName(player.gameObject, "m_Sprite"); driver.AddFromName(player.gameObject, "m_Enabled"); } } base.GatherProperties(director, driver); } } internal readonly struct FrameAnimationTimelineClipInfo { internal TimelineClip TimelineClip { get; } internal FrameAnimationTimelineClip Asset { get; } internal double Start { get; } internal double End { get; } internal int Order { get; } internal FrameAnimationTimelineClipInfo(TimelineClip timelineClip, int order) { TimelineClip = timelineClip; Asset = timelineClip?.asset as FrameAnimationTimelineClip; Start = timelineClip?.start ?? 0d; End = timelineClip?.end ?? 0d; Order = order; } } internal sealed class FrameAnimationTrackMixerBehaviour : PlayableBehaviour { private const double TimeEpsilon = 0.0000001d; private IReadOnlyList clips = Array.Empty(); private FrameClipPlayer currentPlayer; private FrameClip terminalClip; private string lastErrorKey = string.Empty; internal void SetClips(IReadOnlyList value) { clips = value ?? Array.Empty(); } public override void ProcessFrame(Playable playable, FrameData info, object playerData) { var player = playerData as FrameClipPlayer; if (player == null) { ReleaseCurrentPlayer(); return; } if (currentPlayer != player) { ReleaseCurrentPlayer(); currentPlayer = player; ReportError(currentPlayer.BeginTimelineControl(), currentPlayer); } var timelineTime = playable.GetTime(); var activeIndex = FindActiveClipIndex(playable, timelineTime); if (activeIndex >= 0) { var clipInfo = clips[activeIndex]; terminalClip = clipInfo.Asset?.FrameClip; var localTime = playable.GetInput(activeIndex).GetTime(); localTime = NormalizeLoopTime(terminalClip, localTime); ReportError(currentPlayer.SampleTimeline(terminalClip, localTime), currentPlayer); return; } var previous = FindPreviousClip(timelineTime); if (previous.HasValue) { terminalClip = previous.Value.Asset?.FrameClip; ReportError(currentPlayer.ApplyTimelineEndBehavior(terminalClip), currentPlayer); } else { terminalClip = null; currentPlayer.RestoreTimelineBaseline(); lastErrorKey = string.Empty; } } public override void OnGraphStop(Playable playable) { ReleaseCurrentPlayer(); } public override void OnPlayableDestroy(Playable playable) { ReleaseCurrentPlayer(); } private int FindActiveClipIndex(Playable playable, double timelineTime) { var selected = -1; var selectedStart = double.NegativeInfinity; var count = Math.Min(playable.GetInputCount(), clips.Count); for (var index = 0; index < count; index++) { var clip = clips[index]; var isInside = timelineTime + TimeEpsilon >= clip.Start && timelineTime < clip.End - TimeEpsilon; if (!isInside || playable.GetInputWeight(index) <= 0f) { continue; } if (clip.Start > selectedStart || (Math.Abs(clip.Start - selectedStart) <= TimeEpsilon && index > selected)) { selected = index; selectedStart = clip.Start; } } return selected; } private FrameAnimationTimelineClipInfo? FindPreviousClip(double timelineTime) { FrameAnimationTimelineClipInfo? selected = null; foreach (var clip in clips) { if (clip.End > timelineTime + TimeEpsilon) { continue; } if (!selected.HasValue || clip.End > selected.Value.End || (Math.Abs(clip.End - selected.Value.End) <= TimeEpsilon && clip.Order > selected.Value.Order)) { selected = clip; } } return selected; } private static double NormalizeLoopTime(FrameClip clip, double localTime) { if (clip == null || clip.DefaultEndBehavior != FrameClipEndBehavior.Loop) { return Math.Max(0d, localTime); } var duration = FrameAnimationTimelineClip.GetEffectiveDuration(clip); if (duration <= 0d) { return 0d; } return Math.Max(0d, localTime) % duration; } private void ReleaseCurrentPlayer() { if (currentPlayer != null) { currentPlayer.ReleaseTimelineControl(terminalClip); } currentPlayer = null; terminalClip = null; lastErrorKey = string.Empty; } private void ReportError(FrameAnimationPlaybackError error, UnityEngine.Object context) { if (error.Code == FrameAnimationPlaybackErrorCode.None) { lastErrorKey = string.Empty; return; } var key = $"{error.Code}:{error.Message}"; if (lastErrorKey == key) { return; } lastErrorKey = key; Debug.LogError($"Frame Animation Timeline 播放失败:{error}", context); } } }