604 lines
24 KiB
C#
604 lines
24 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
|
|
namespace AibisDream.FrameAnimation.Editor
|
|
{
|
|
internal enum FrameAnimationPreviewTargetKind { None, Clip, Node, Flow }
|
|
internal enum FrameAnimationPreviewState { Stopped, Playing, Paused, Failed }
|
|
internal enum NodePreviewPolicy { Static, SelectedOnly, AllVisible }
|
|
internal enum FrameAnimationPreviewBackground { Checkerboard, Dark, Light }
|
|
internal enum FrameAnimationPreviewZoomMode { Fit, One, Two, Three, Four, Eight, Manual }
|
|
internal enum FrameAnimationFlowElementState { None, Upcoming, Current, Passed }
|
|
|
|
internal readonly struct FrameAnimationPreviewSegment
|
|
{
|
|
public int StepIndex { get; }
|
|
public int FrameIndex { get; }
|
|
public FrameClip Clip { get; }
|
|
public string NodeId { get; }
|
|
public double StartSeconds { get; }
|
|
public double DurationSeconds { get; }
|
|
public bool IsBlocked { get; }
|
|
|
|
public FrameAnimationPreviewSegment(
|
|
int stepIndex,
|
|
int frameIndex,
|
|
ResolvedPlaybackStep step,
|
|
double startSeconds,
|
|
double durationSeconds,
|
|
bool isBlocked)
|
|
{
|
|
StepIndex = stepIndex;
|
|
FrameIndex = frameIndex;
|
|
Clip = step.Clip;
|
|
NodeId = step.NodeId;
|
|
StartSeconds = startSeconds;
|
|
DurationSeconds = durationSeconds;
|
|
IsBlocked = isBlocked;
|
|
}
|
|
}
|
|
|
|
internal sealed class FrameAnimationPreviewTimeline
|
|
{
|
|
private const double Epsilon = 0.000000001d;
|
|
private readonly List<FrameAnimationPreviewSegment> segments = new List<FrameAnimationPreviewSegment>();
|
|
|
|
public IReadOnlyList<FrameAnimationPreviewSegment> Segments => segments;
|
|
public double DisplayDurationSeconds { get; }
|
|
public bool IsInfinite { get; }
|
|
public bool IsBlocked { get; }
|
|
public int BlockedSegmentIndex { get; } = -1;
|
|
|
|
public FrameAnimationPreviewTimeline(ResolvedPlaybackPlan plan)
|
|
{
|
|
if (plan?.Steps == null || plan.Steps.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var cursor = 0d;
|
|
var blockedIndex = -1;
|
|
for (var stepIndex = 0; stepIndex < plan.Steps.Count; stepIndex++)
|
|
{
|
|
var step = plan.Steps[stepIndex];
|
|
if (step?.Clip == null)
|
|
{
|
|
continue;
|
|
}
|
|
for (var frameIndex = 0; frameIndex < step.Clip.FrameCount; frameIndex++)
|
|
{
|
|
var frame = step.Clip.Frames[frameIndex];
|
|
if (step.PlaybackSpeed == 0f)
|
|
{
|
|
blockedIndex = segments.Count;
|
|
segments.Add(new FrameAnimationPreviewSegment(
|
|
stepIndex, frameIndex, step, cursor, 0d, true));
|
|
break;
|
|
}
|
|
|
|
var duration = frame.DurationMs / 1000d / step.PlaybackSpeed;
|
|
segments.Add(new FrameAnimationPreviewSegment(
|
|
stepIndex, frameIndex, step, cursor, duration, false));
|
|
cursor += duration;
|
|
}
|
|
if (blockedIndex >= 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
DisplayDurationSeconds = cursor;
|
|
BlockedSegmentIndex = blockedIndex;
|
|
IsBlocked = blockedIndex >= 0;
|
|
IsInfinite = IsBlocked || plan.Steps[plan.Steps.Count - 1].TerminalEndBehavior == FrameClipEndBehavior.Loop;
|
|
}
|
|
|
|
public int FindSegmentIndex(double positionSeconds)
|
|
{
|
|
if (segments.Count == 0)
|
|
{
|
|
return -1;
|
|
}
|
|
var position = Math.Max(0d, positionSeconds);
|
|
for (var index = 0; index < segments.Count; index++)
|
|
{
|
|
var segment = segments[index];
|
|
if (segment.IsBlocked || position < segment.StartSeconds + segment.DurationSeconds - Epsilon)
|
|
{
|
|
return index;
|
|
}
|
|
}
|
|
return segments.Count - 1;
|
|
}
|
|
|
|
public double GetSeekSeconds(int segmentIndex)
|
|
{
|
|
return segmentIndex >= 0 && segmentIndex < segments.Count
|
|
? segments[segmentIndex].StartSeconds
|
|
: 0d;
|
|
}
|
|
|
|
public double GetPosition(FrameAnimationPlaybackSnapshot snapshot)
|
|
{
|
|
if (!snapshot.HasStarted || segments.Count == 0)
|
|
{
|
|
return 0d;
|
|
}
|
|
var segment = segments.FirstOrDefault(item =>
|
|
item.StepIndex == snapshot.StepIndex && item.FrameIndex == snapshot.FrameIndex);
|
|
if (segment.Clip == null)
|
|
{
|
|
return snapshot.IsCompleted ? DisplayDurationSeconds : 0d;
|
|
}
|
|
if (snapshot.IsCompleted && !IsInfinite)
|
|
{
|
|
return DisplayDurationSeconds;
|
|
}
|
|
var elapsed = snapshot.StepSpeed > 0f
|
|
? snapshot.FrameElapsedSeconds / snapshot.StepSpeed
|
|
: 0d;
|
|
return Math.Min(DisplayDurationSeconds, segment.StartSeconds + elapsed);
|
|
}
|
|
}
|
|
|
|
internal readonly struct FrameAnimationPreviewDisplay
|
|
{
|
|
public Sprite Sprite { get; }
|
|
public bool IsEmpty { get; }
|
|
public bool IsActive { get; }
|
|
public FrameAnimationFlowElementState FlowState { get; }
|
|
|
|
public FrameAnimationPreviewDisplay(
|
|
Sprite sprite,
|
|
bool isEmpty,
|
|
bool isActive,
|
|
FrameAnimationFlowElementState flowState)
|
|
{
|
|
Sprite = sprite;
|
|
IsEmpty = isEmpty;
|
|
IsActive = isActive;
|
|
FlowState = flowState;
|
|
}
|
|
}
|
|
|
|
internal sealed class FrameAnimationPreviewCoordinator
|
|
{
|
|
private sealed class NodeRun
|
|
{
|
|
public AnimationNode Node;
|
|
public FrameAnimationPlaybackSession Session;
|
|
public Sprite Sprite;
|
|
}
|
|
|
|
private readonly Dictionary<string, NodeRun> automaticRuns = new Dictionary<string, NodeRun>();
|
|
private FrameAnimationGraph graph;
|
|
private object target;
|
|
private ResolvedPlaybackPlan plan;
|
|
private FrameAnimationPlaybackSession session;
|
|
private Sprite currentSprite;
|
|
private bool terminalTransparent;
|
|
private bool suppressSelectedNode;
|
|
private bool flowPresentationActive;
|
|
|
|
public event Action Changed;
|
|
|
|
public FrameAnimationPreviewTargetKind TargetKind { get; private set; }
|
|
public FrameAnimationPreviewState State { get; private set; } = FrameAnimationPreviewState.Stopped;
|
|
public NodePreviewPolicy NodePolicy { get; set; } = NodePreviewPolicy.SelectedOnly;
|
|
public FrameAnimationPreviewBackground Background { get; set; } = FrameAnimationPreviewBackground.Checkerboard;
|
|
public FrameAnimationPreviewZoomMode ZoomMode { get; set; } = FrameAnimationPreviewZoomMode.Fit;
|
|
public float ManualZoom { get; set; } = 1f;
|
|
public float PreviewSpeed { get; set; } = 1f;
|
|
public FrameAnimationPreviewTimeline Timeline { get; private set; }
|
|
public FrameAnimationPlaybackError Error { get; private set; } = FrameAnimationPlaybackError.None;
|
|
public double PositionSeconds { get; private set; }
|
|
public string TransitionFromNodeId { get; private set; } = string.Empty;
|
|
public string TransitionToNodeId { get; private set; } = string.Empty;
|
|
public object Target => target;
|
|
public bool IsFlowPreview => TargetKind == FrameAnimationPreviewTargetKind.Flow &&
|
|
flowPresentationActive && State != FrameAnimationPreviewState.Failed;
|
|
public FrameAnimationPlaybackSnapshot Snapshot => session != null
|
|
? session.Snapshot
|
|
: default;
|
|
public Sprite CurrentSprite => terminalTransparent ? null : currentSprite;
|
|
|
|
public void SetGraph(FrameAnimationGraph value)
|
|
{
|
|
if (ReferenceEquals(graph, value))
|
|
{
|
|
return;
|
|
}
|
|
graph = value;
|
|
ClearTarget();
|
|
automaticRuns.Clear();
|
|
}
|
|
|
|
public void SetTarget(FrameAnimationPreviewTargetKind kind, object value)
|
|
{
|
|
if (TargetKind == kind && ReferenceEquals(target, value))
|
|
{
|
|
return;
|
|
}
|
|
TargetKind = kind;
|
|
target = value;
|
|
suppressSelectedNode = false;
|
|
BuildTarget(false, 0d);
|
|
if (kind == FrameAnimationPreviewTargetKind.Node && NodePolicy != NodePreviewPolicy.Static && session != null)
|
|
{
|
|
State = FrameAnimationPreviewState.Playing;
|
|
}
|
|
Changed?.Invoke();
|
|
}
|
|
|
|
public void ClearTarget()
|
|
{
|
|
TargetKind = FrameAnimationPreviewTargetKind.None;
|
|
target = null;
|
|
plan = null;
|
|
session = null;
|
|
Timeline = null;
|
|
currentSprite = null;
|
|
terminalTransparent = false;
|
|
Error = FrameAnimationPlaybackError.None;
|
|
PositionSeconds = 0d;
|
|
State = FrameAnimationPreviewState.Stopped;
|
|
flowPresentationActive = false;
|
|
TransitionFromNodeId = string.Empty;
|
|
TransitionToNodeId = string.Empty;
|
|
Changed?.Invoke();
|
|
}
|
|
|
|
public void RefreshTarget(bool topologyChanged)
|
|
{
|
|
if (TargetKind == FrameAnimationPreviewTargetKind.None)
|
|
{
|
|
return;
|
|
}
|
|
if (topologyChanged && TargetKind == FrameAnimationPreviewTargetKind.Flow)
|
|
{
|
|
Stop();
|
|
BuildTarget(false, 0d);
|
|
return;
|
|
}
|
|
var wasPlaying = State == FrameAnimationPreviewState.Playing;
|
|
var position = PositionSeconds;
|
|
BuildTarget(position > 0d, position);
|
|
if (wasPlaying && session != null)
|
|
{
|
|
State = FrameAnimationPreviewState.Playing;
|
|
flowPresentationActive = TargetKind == FrameAnimationPreviewTargetKind.Flow;
|
|
}
|
|
Changed?.Invoke();
|
|
}
|
|
|
|
public void SetNodePolicy(NodePreviewPolicy value)
|
|
{
|
|
NodePolicy = value;
|
|
automaticRuns.Clear();
|
|
suppressSelectedNode = false;
|
|
if (TargetKind == FrameAnimationPreviewTargetKind.Node)
|
|
{
|
|
BuildTarget(false, 0d);
|
|
if (session != null && value != NodePreviewPolicy.Static)
|
|
{
|
|
State = FrameAnimationPreviewState.Playing;
|
|
}
|
|
}
|
|
Changed?.Invoke();
|
|
}
|
|
|
|
public void Play()
|
|
{
|
|
if (session == null)
|
|
{
|
|
BuildTarget(false, 0d);
|
|
}
|
|
if (session == null)
|
|
{
|
|
return;
|
|
}
|
|
if (session.Snapshot.IsCompleted)
|
|
{
|
|
session.Restart(ApplySprite);
|
|
PositionSeconds = 0d;
|
|
}
|
|
terminalTransparent = false;
|
|
suppressSelectedNode = false;
|
|
flowPresentationActive = TargetKind == FrameAnimationPreviewTargetKind.Flow;
|
|
State = FrameAnimationPreviewState.Playing;
|
|
Changed?.Invoke();
|
|
}
|
|
|
|
public void Pause()
|
|
{
|
|
if (session == null || State != FrameAnimationPreviewState.Playing) return;
|
|
State = FrameAnimationPreviewState.Paused;
|
|
Changed?.Invoke();
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
if (session != null)
|
|
{
|
|
session.Restart(ApplySprite);
|
|
}
|
|
State = FrameAnimationPreviewState.Stopped;
|
|
PositionSeconds = 0d;
|
|
terminalTransparent = false;
|
|
suppressSelectedNode = TargetKind == FrameAnimationPreviewTargetKind.Node;
|
|
flowPresentationActive = false;
|
|
TransitionFromNodeId = string.Empty;
|
|
TransitionToNodeId = string.Empty;
|
|
Changed?.Invoke();
|
|
}
|
|
|
|
public void Restart()
|
|
{
|
|
if (session == null)
|
|
{
|
|
BuildTarget(false, 0d);
|
|
}
|
|
session?.Restart(ApplySprite);
|
|
PositionSeconds = 0d;
|
|
terminalTransparent = false;
|
|
suppressSelectedNode = false;
|
|
flowPresentationActive = TargetKind == FrameAnimationPreviewTargetKind.Flow;
|
|
if (session != null) State = FrameAnimationPreviewState.Playing;
|
|
Changed?.Invoke();
|
|
}
|
|
|
|
public void Step(int direction)
|
|
{
|
|
if (session == null || Timeline == null || Timeline.Segments.Count == 0) return;
|
|
var current = Timeline.FindSegmentIndex(PositionSeconds);
|
|
var next = Mathf.Clamp(current + Math.Sign(direction), 0, Timeline.Segments.Count - 1);
|
|
Seek(Timeline.GetSeekSeconds(next));
|
|
}
|
|
|
|
public void Seek(double seconds)
|
|
{
|
|
if (session == null || Timeline == null) return;
|
|
var maximum = Timeline.DisplayDurationSeconds;
|
|
var targetSeconds = Math.Max(0d, Math.Min(maximum, seconds));
|
|
if (Timeline.IsInfinite && maximum > 0d && targetSeconds >= maximum)
|
|
{
|
|
targetSeconds = Math.Max(0d, maximum - 0.000001d);
|
|
}
|
|
terminalTransparent = false;
|
|
flowPresentationActive = TargetKind == FrameAnimationPreviewTargetKind.Flow;
|
|
var result = session.Seek(targetSeconds, ApplySprite);
|
|
ApplyEvaluation(result);
|
|
PositionSeconds = targetSeconds;
|
|
if (State != FrameAnimationPreviewState.Failed)
|
|
{
|
|
State = FrameAnimationPreviewState.Paused;
|
|
}
|
|
Changed?.Invoke();
|
|
}
|
|
|
|
public bool Tick(double deltaSeconds, IReadOnlyCollection<AnimationNode> visibleNodes)
|
|
{
|
|
var changed = false;
|
|
TransitionFromNodeId = string.Empty;
|
|
TransitionToNodeId = string.Empty;
|
|
if (State == FrameAnimationPreviewState.Playing && session != null)
|
|
{
|
|
var before = session.Snapshot;
|
|
var result = session.Evaluate(deltaSeconds, PreviewSpeed, ApplySprite);
|
|
var after = session.Snapshot;
|
|
if (before.StepIndex != after.StepIndex)
|
|
{
|
|
TransitionFromNodeId = before.NodeId;
|
|
TransitionToNodeId = after.NodeId;
|
|
}
|
|
ApplyEvaluation(result);
|
|
PositionSeconds = Timeline?.GetPosition(after) ?? 0d;
|
|
if (result.IsCompleted && TargetKind == FrameAnimationPreviewTargetKind.Node &&
|
|
NodePolicy != NodePreviewPolicy.Static && !suppressSelectedNode && session != null)
|
|
{
|
|
session.Restart(ApplySprite);
|
|
State = FrameAnimationPreviewState.Playing;
|
|
PositionSeconds = 0d;
|
|
terminalTransparent = false;
|
|
}
|
|
changed = true;
|
|
}
|
|
|
|
if (!IsFlowPreview)
|
|
{
|
|
changed |= TickAutomaticNodes(deltaSeconds, visibleNodes);
|
|
}
|
|
else if (automaticRuns.Count > 0)
|
|
{
|
|
automaticRuns.Clear();
|
|
changed = true;
|
|
}
|
|
|
|
if (changed) Changed?.Invoke();
|
|
return changed;
|
|
}
|
|
|
|
public FrameAnimationPreviewDisplay GetNodeDisplay(AnimationNode node)
|
|
{
|
|
if (node == null)
|
|
{
|
|
return default;
|
|
}
|
|
if (IsFlowPreview && plan != null)
|
|
{
|
|
var current = session?.Snapshot.StepIndex ?? 0;
|
|
var stepIndex = IndexOfNode(plan, node.InternalId);
|
|
var flowState = stepIndex < 0 ? FrameAnimationFlowElementState.None :
|
|
stepIndex < current ? FrameAnimationFlowElementState.Passed :
|
|
stepIndex == current ? FrameAnimationFlowElementState.Current : FrameAnimationFlowElementState.Upcoming;
|
|
if (flowState == FrameAnimationFlowElementState.Current)
|
|
{
|
|
return new FrameAnimationPreviewDisplay(CurrentSprite, CurrentSprite == null, true, flowState);
|
|
}
|
|
return Representative(node, flowState);
|
|
}
|
|
if (TargetKind == FrameAnimationPreviewTargetKind.Node && ReferenceEquals(target, node) && session != null)
|
|
{
|
|
if (NodePolicy == NodePreviewPolicy.Static && State == FrameAnimationPreviewState.Stopped &&
|
|
!suppressSelectedNode && !session.Snapshot.IsCompleted)
|
|
{
|
|
return Representative(node, FrameAnimationFlowElementState.None);
|
|
}
|
|
return new FrameAnimationPreviewDisplay(CurrentSprite, CurrentSprite == null, State == FrameAnimationPreviewState.Playing,
|
|
FrameAnimationFlowElementState.None);
|
|
}
|
|
if (automaticRuns.TryGetValue(node.InternalId, out var run))
|
|
{
|
|
return new FrameAnimationPreviewDisplay(run.Sprite, run.Sprite == null, true, FrameAnimationFlowElementState.None);
|
|
}
|
|
return Representative(node, FrameAnimationFlowElementState.None);
|
|
}
|
|
|
|
public FrameAnimationFlowElementState GetEdgeState(AnimationEdge edge)
|
|
{
|
|
if (!IsFlowPreview || plan == null || edge == null) return FrameAnimationFlowElementState.None;
|
|
if (edge.FromNodeId == TransitionFromNodeId && edge.ToNodeId == TransitionToNodeId)
|
|
{
|
|
return FrameAnimationFlowElementState.Current;
|
|
}
|
|
var from = IndexOfNode(plan, edge.FromNodeId);
|
|
var to = IndexOfNode(plan, edge.ToNodeId);
|
|
if (from < 0 || to != from + 1) return FrameAnimationFlowElementState.None;
|
|
var current = session?.Snapshot.StepIndex ?? 0;
|
|
return to <= current ? FrameAnimationFlowElementState.Passed : FrameAnimationFlowElementState.Upcoming;
|
|
}
|
|
|
|
private bool TickAutomaticNodes(double deltaSeconds, IReadOnlyCollection<AnimationNode> visibleNodes)
|
|
{
|
|
IEnumerable<AnimationNode> desired = Array.Empty<AnimationNode>();
|
|
if (NodePolicy == NodePreviewPolicy.SelectedOnly && TargetKind == FrameAnimationPreviewTargetKind.Node &&
|
|
target is AnimationNode selected && !suppressSelectedNode)
|
|
{
|
|
desired = new[] { selected };
|
|
}
|
|
else if (NodePolicy == NodePreviewPolicy.AllVisible)
|
|
{
|
|
desired = visibleNodes ?? Array.Empty<AnimationNode>();
|
|
}
|
|
var desiredNodes = desired.Where(node => node != null &&
|
|
!(TargetKind == FrameAnimationPreviewTargetKind.Node && ReferenceEquals(target, node)))
|
|
.GroupBy(node => node.InternalId).Select(group => group.First()).ToArray();
|
|
var desiredIds = new HashSet<string>(desiredNodes.Select(node => node.InternalId));
|
|
foreach (var id in automaticRuns.Keys.Where(id => !desiredIds.Contains(id)).ToArray())
|
|
{
|
|
automaticRuns.Remove(id);
|
|
}
|
|
foreach (var node in desiredNodes)
|
|
{
|
|
if (!automaticRuns.TryGetValue(node.InternalId, out var run))
|
|
{
|
|
if (!FrameAnimationResolver.TryResolveNode(graph, node, out var nodePlan, out _)) continue;
|
|
run = new NodeRun { Node = node, Session = new FrameAnimationPlaybackSession(nodePlan) };
|
|
run.Session.Start(sprite => run.Sprite = sprite);
|
|
automaticRuns[node.InternalId] = run;
|
|
}
|
|
var evaluation = run.Session.Evaluate(deltaSeconds, PreviewSpeed, sprite => run.Sprite = sprite);
|
|
if (evaluation.IsCompleted)
|
|
{
|
|
run.Session.Restart(sprite => run.Sprite = sprite);
|
|
}
|
|
else if (evaluation.IsFailed)
|
|
{
|
|
automaticRuns.Remove(node.InternalId);
|
|
}
|
|
}
|
|
return desiredNodes.Length > 0 || automaticRuns.Count > 0;
|
|
}
|
|
|
|
private void BuildTarget(bool seek, double position)
|
|
{
|
|
plan = null;
|
|
session = null;
|
|
Timeline = null;
|
|
currentSprite = null;
|
|
terminalTransparent = false;
|
|
PositionSeconds = 0d;
|
|
Error = FrameAnimationPlaybackError.None;
|
|
State = FrameAnimationPreviewState.Stopped;
|
|
flowPresentationActive = false;
|
|
var resolved = TargetKind switch
|
|
{
|
|
FrameAnimationPreviewTargetKind.Clip when target is FrameClip clip =>
|
|
TryResolvePreviewClip(clip, out plan, out var clipError)
|
|
? FrameAnimationPlaybackError.None : clipError,
|
|
FrameAnimationPreviewTargetKind.Node when target is AnimationNode node =>
|
|
FrameAnimationResolver.TryResolveNode(graph, node, out plan, out var nodeError)
|
|
? FrameAnimationPlaybackError.None : nodeError,
|
|
FrameAnimationPreviewTargetKind.Flow when target is AnimationFlow flow =>
|
|
FrameAnimationResolver.TryResolve(graph, flow.Id, default, out plan, out var flowError)
|
|
? FrameAnimationPlaybackError.None : flowError,
|
|
_ => new FrameAnimationPlaybackError(FrameAnimationPlaybackErrorCode.PlayableNotFound, "没有可预览目标。")
|
|
};
|
|
if (resolved.Code != FrameAnimationPlaybackErrorCode.None || plan == null)
|
|
{
|
|
Error = resolved;
|
|
State = TargetKind == FrameAnimationPreviewTargetKind.None
|
|
? FrameAnimationPreviewState.Stopped : FrameAnimationPreviewState.Failed;
|
|
return;
|
|
}
|
|
Timeline = new FrameAnimationPreviewTimeline(plan);
|
|
session = new FrameAnimationPlaybackSession(plan);
|
|
session.Start(ApplySprite);
|
|
if (seek && position > 0d)
|
|
{
|
|
Seek(position);
|
|
}
|
|
}
|
|
|
|
private bool TryResolvePreviewClip(
|
|
FrameClip clip,
|
|
out ResolvedPlaybackPlan resolvedPlan,
|
|
out FrameAnimationPlaybackError error)
|
|
{
|
|
return graph != null
|
|
? FrameAnimationResolver.TryResolve(graph, clip.Id, default, out resolvedPlan, out error)
|
|
: FrameAnimationResolver.TryResolveClip(clip, default, out resolvedPlan, out error);
|
|
}
|
|
|
|
private void ApplyEvaluation(FrameAnimationSessionEvaluation evaluation)
|
|
{
|
|
if (evaluation.IsFailed)
|
|
{
|
|
Error = evaluation.Error;
|
|
State = FrameAnimationPreviewState.Failed;
|
|
flowPresentationActive = false;
|
|
return;
|
|
}
|
|
if (!evaluation.IsCompleted) return;
|
|
State = FrameAnimationPreviewState.Stopped;
|
|
terminalTransparent = evaluation.EndBehavior == FrameClipEndBehavior.Clear ||
|
|
evaluation.EndBehavior == FrameClipEndBehavior.HideTarget;
|
|
}
|
|
|
|
private void ApplySprite(Sprite sprite)
|
|
{
|
|
currentSprite = sprite;
|
|
terminalTransparent = false;
|
|
}
|
|
|
|
private FrameAnimationPreviewDisplay Representative(AnimationNode node, FrameAnimationFlowElementState flowState)
|
|
{
|
|
var clip = graph?.Clips.FirstOrDefault(item => item != null && item.Id == node.ClipId);
|
|
var sprite = clip?.Frames.FirstOrDefault(frame => frame?.Sprite != null)?.Sprite;
|
|
return new FrameAnimationPreviewDisplay(sprite, sprite == null, false, flowState);
|
|
}
|
|
|
|
private static int IndexOfNode(ResolvedPlaybackPlan targetPlan, string nodeId)
|
|
{
|
|
if (targetPlan?.Steps == null) return -1;
|
|
for (var index = 0; index < targetPlan.Steps.Count; index++)
|
|
{
|
|
if (targetPlan.Steps[index].NodeId == nodeId) return index;
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
}
|