Files
aibis-dream/Assets/Scripts/FrameAnimation/Timeline/FrameAnimationTrack.cs
T

208 lines
7.2 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();
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 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);
}
}
}