224 lines
7.7 KiB
C#
224 lines
7.7 KiB
C#
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<FrameAnimationTrackMixerBehaviour>.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<SpriteRenderer>();
|
|
if (spriteRenderer != null)
|
|
{
|
|
driver.AddFromName<SpriteRenderer>(player.gameObject, "m_Sprite");
|
|
driver.AddFromName<SpriteRenderer>(player.gameObject, "m_Enabled");
|
|
}
|
|
|
|
var image = player.GetComponent<Image>();
|
|
if (image != null)
|
|
{
|
|
driver.AddFromName<Image>(player.gameObject, "m_Sprite");
|
|
driver.AddFromName<Image>(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<FrameAnimationTimelineClipInfo> clips =
|
|
Array.Empty<FrameAnimationTimelineClipInfo>();
|
|
private FrameClipPlayer currentPlayer;
|
|
private FrameClip terminalClip;
|
|
private string lastErrorKey = string.Empty;
|
|
|
|
internal void SetClips(IReadOnlyList<FrameAnimationTimelineClipInfo> value)
|
|
{
|
|
clips = value ?? Array.Empty<FrameAnimationTimelineClipInfo>();
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|