Files
aibis-dream/Assets/Tests/FrameAnimation/EditMode/FrameAnimationPreviewTests.cs
T

249 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using AibisDream.FrameAnimation.Editor;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace AibisDream.FrameAnimation.Tests.EditMode
{
public sealed class FrameAnimationPreviewTests
{
private readonly List<UnityEngine.Object> objects = new List<UnityEngine.Object>();
[TearDown]
public void TearDown()
{
foreach (var item in objects.Where(item => item != null))
{
UnityEngine.Object.DestroyImmediate(item);
}
objects.Clear();
}
[Test]
public void SessionSnapshotAndSeek_UseTheRuntimeEvaluator()
{
var clip = Clip("Clip", 2f, FrameClipEndBehavior.HoldLastFrame, 100, 200, 300);
var graph = Graph(new[] { clip }, Array.Empty<AnimationNode>(), Array.Empty<AnimationEdge>(),
Array.Empty<AnimationFlow>());
Assert.That(FrameAnimationResolver.TryResolve(graph, clip.Id, default, out var plan, out _), Is.True);
var session = new FrameAnimationPlaybackSession(plan);
session.Start(_ => { });
session.Seek(0.075d, _ => { });
Assert.That(session.Snapshot.StepIndex, Is.EqualTo(0));
Assert.That(session.Snapshot.FrameIndex, Is.EqualTo(1));
Assert.That(session.Snapshot.FrameElapsedSeconds, Is.EqualTo(0.05d).Within(0.000001d));
Assert.That(session.Snapshot.StepSpeed, Is.EqualTo(2f));
}
[Test]
public void NodeResolver_AppliesNodeOverridesWithoutChangingClip()
{
var clip = Clip("Clip", 0.5f, FrameClipEndBehavior.HoldLastFrame, 100);
var node = new AnimationNode(clip.Id, speedOverride: 3f,
endBehaviorOverride: FrameClipEndBehavior.HideTarget);
var graph = Graph(new[] { clip }, new[] { node }, Array.Empty<AnimationEdge>(),
Array.Empty<AnimationFlow>());
Assert.That(FrameAnimationResolver.TryResolveNode(graph, node, out var plan, out var error),
Is.True, error.ToString());
Assert.That(plan.Steps.Single().PlaybackSpeed, Is.EqualTo(3f));
Assert.That(plan.Steps.Single().TerminalEndBehavior, Is.EqualTo(FrameClipEndBehavior.HideTarget));
Assert.That(plan.Steps.Single().NodeId, Is.EqualTo(node.InternalId));
Assert.That(clip.Speed, Is.EqualTo(0.5f));
}
[Test]
public void Timeline_FlattensFlowUsingEffectiveStepDurations()
{
var first = Clip("First", 1f, FrameClipEndBehavior.HoldLastFrame, 100, 100);
var second = Clip("Second", 1f, FrameClipEndBehavior.HoldLastFrame, 300);
var firstNode = new AnimationNode(first.Id, speedOverride: 2f);
var secondNode = new AnimationNode(second.Id, speedOverride: 0.5f);
var edge = new AnimationEdge(firstNode.InternalId, secondNode.InternalId);
var flow = new AnimationFlow("Flow", "Flow", firstNode.InternalId);
var graph = Graph(new[] { first, second }, new[] { firstNode, secondNode }, new[] { edge }, new[] { flow });
Assert.That(FrameAnimationResolver.TryResolve(graph, flow.Id, default, out var plan, out _), Is.True);
var timeline = new FrameAnimationPreviewTimeline(plan);
Assert.That(timeline.Segments.Count, Is.EqualTo(3));
Assert.That(timeline.Segments[2].NodeId, Is.EqualTo(secondNode.InternalId));
Assert.That(timeline.Segments[2].StartSeconds, Is.EqualTo(0.1d).Within(0.000001d));
Assert.That(timeline.DisplayDurationSeconds, Is.EqualTo(0.7d).Within(0.000001d));
Assert.That(timeline.IsInfinite, Is.False);
}
[Test]
public void Timeline_LoopUsesOneCycle_AndZeroSpeedStopsReachability()
{
var loopClip = Clip("Loop", 1f, FrameClipEndBehavior.Loop, 100, 200);
var loopNode = new AnimationNode(loopClip.Id, endBehaviorOverride: FrameClipEndBehavior.Loop);
var loopFlow = new AnimationFlow("LoopFlow", "Loop", loopNode.InternalId);
var loopGraph = Graph(new[] { loopClip }, new[] { loopNode }, Array.Empty<AnimationEdge>(), new[] { loopFlow });
FrameAnimationResolver.TryResolve(loopGraph, loopFlow.Id, default, out var loopPlan, out _);
var loopTimeline = new FrameAnimationPreviewTimeline(loopPlan);
Assert.That(loopTimeline.IsInfinite, Is.True);
Assert.That(loopTimeline.Segments.Count, Is.EqualTo(2));
Assert.That(loopTimeline.DisplayDurationSeconds, Is.EqualTo(0.3d).Within(0.000001d));
var blockedClip = Clip("Blocked", 1f, FrameClipEndBehavior.HoldLastFrame, 100, 100);
var blockedNode = new AnimationNode(blockedClip.Id, speedOverride: 0f);
var blockedGraph = Graph(new[] { blockedClip }, new[] { blockedNode }, Array.Empty<AnimationEdge>(),
Array.Empty<AnimationFlow>());
FrameAnimationResolver.TryResolveNode(blockedGraph, blockedNode, out var blockedPlan, out _);
var blockedTimeline = new FrameAnimationPreviewTimeline(blockedPlan);
Assert.That(blockedTimeline.IsBlocked, Is.True);
Assert.That(blockedTimeline.IsInfinite, Is.True);
Assert.That(blockedTimeline.Segments.Count, Is.EqualTo(1));
Assert.That(blockedTimeline.Segments[0].IsBlocked, Is.True);
}
[Test]
public void Coordinator_FlowPreviewCrossesNodesAndRestoresAutomaticPolicyOnStop()
{
var first = Clip("First", 1f, FrameClipEndBehavior.HoldLastFrame, 100);
var second = Clip("Second", 1f, FrameClipEndBehavior.Loop, 100);
var firstNode = new AnimationNode(first.Id);
var secondNode = new AnimationNode(second.Id, endBehaviorOverride: FrameClipEndBehavior.Loop);
var edge = new AnimationEdge(firstNode.InternalId, secondNode.InternalId);
var flow = new AnimationFlow("Flow", "Flow", firstNode.InternalId);
var graph = Graph(new[] { first, second }, new[] { firstNode, secondNode }, new[] { edge }, new[] { flow });
var coordinator = new FrameAnimationPreviewCoordinator();
coordinator.SetGraph(graph);
coordinator.SetTarget(FrameAnimationPreviewTargetKind.Flow, flow);
coordinator.Play();
coordinator.Tick(0.1d, graph.Nodes);
Assert.That(coordinator.IsFlowPreview, Is.True);
Assert.That(coordinator.Snapshot.NodeId, Is.EqualTo(secondNode.InternalId));
Assert.That(coordinator.GetNodeDisplay(firstNode).FlowState, Is.EqualTo(FrameAnimationFlowElementState.Passed));
Assert.That(coordinator.GetNodeDisplay(secondNode).FlowState, Is.EqualTo(FrameAnimationFlowElementState.Current));
coordinator.Stop();
Assert.That(coordinator.IsFlowPreview, Is.False);
Assert.That(coordinator.NodePolicy, Is.EqualTo(NodePreviewPolicy.SelectedOnly));
}
[Test]
public void Coordinator_FlowSeekAndStepCrossNodeBoundaries()
{
var first = Clip("First", 1f, FrameClipEndBehavior.HoldLastFrame, 100);
var second = Clip("Second", 1f, FrameClipEndBehavior.HoldLastFrame, 200, 200);
var firstNode = new AnimationNode(first.Id);
var secondNode = new AnimationNode(second.Id);
var flow = new AnimationFlow("Flow", "Flow", firstNode.InternalId);
var graph = Graph(new[] { first, second }, new[] { firstNode, secondNode },
new[] { new AnimationEdge(firstNode.InternalId, secondNode.InternalId) }, new[] { flow });
var coordinator = new FrameAnimationPreviewCoordinator();
coordinator.SetGraph(graph);
coordinator.SetTarget(FrameAnimationPreviewTargetKind.Flow, flow);
coordinator.Seek(0.1d);
Assert.That(coordinator.Snapshot.NodeId, Is.EqualTo(secondNode.InternalId));
Assert.That(coordinator.Snapshot.FrameIndex, Is.EqualTo(0));
coordinator.Step(1);
Assert.That(coordinator.Snapshot.NodeId, Is.EqualTo(secondNode.InternalId));
Assert.That(coordinator.Snapshot.FrameIndex, Is.EqualTo(1));
coordinator.Step(-1);
Assert.That(coordinator.Snapshot.FrameIndex, Is.EqualTo(0));
}
[Test]
public void GraphViewPreview_RendersOnePreviewPerNodeWithoutMutatingGraph()
{
var clip = Clip("Clip", 1f, FrameClipEndBehavior.Loop, 100);
var first = new AnimationNode(clip.Id);
var second = new AnimationNode(clip.Id);
var graph = Graph(new[] { clip }, new[] { first, second }, Array.Empty<AnimationEdge>(),
Array.Empty<AnimationFlow>());
var before = JsonUtility.ToJson(graph);
var view = new FrameAnimationGraphView();
view.Bind(graph, Array.Empty<FrameAnimationEditorIssue>(), string.Empty);
var coordinator = new FrameAnimationPreviewCoordinator();
coordinator.SetGraph(graph);
coordinator.SetNodePolicy(NodePreviewPolicy.AllVisible);
view.ApplyPreview(coordinator);
Assert.That(view.Query<FrameAnimationPreviewElement>().ToList().Count, Is.EqualTo(2));
Assert.That(JsonUtility.ToJson(graph), Is.EqualTo(before));
}
[Test]
public void PreviewSample_IsIsolatedAndStructurallyPlayable()
{
var sample = AssetDatabase.LoadAssetAtPath<FrameAnimationGraph>(
"Assets/GameContent/Test/FrameAnimation/Preview/PreviewSampleGraph.asset");
Assert.That(sample, Is.Not.Null);
Assert.That(sample.Clips.Count, Is.EqualTo(3));
Assert.That(sample.Flows.Count, Is.EqualTo(5));
Assert.That(sample.Clips.Any(item => item.Frames.Any(frame => frame.Sprite == null)), Is.True);
foreach (var flow in sample.Flows)
{
Assert.That(FrameAnimationResolver.TryResolve(sample, flow.Id, default, out _, out var error),
Is.True, error.ToString());
}
}
[Test]
public void Workbench_CreateGUIBuildsSharedAndClipPreviewElements()
{
var sample = AssetDatabase.LoadAssetAtPath<FrameAnimationGraph>(
"Assets/GameContent/Test/FrameAnimation/Preview/PreviewSampleGraph.asset");
var window = ScriptableObject.CreateInstance<FrameAnimationGraphEditorWindow>();
try
{
typeof(FrameAnimationGraphEditorWindow).GetField("graph",
BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(window, sample);
typeof(FrameAnimationGraphEditorWindow).GetMethod("CreateGUI",
BindingFlags.Instance | BindingFlags.NonPublic)?.Invoke(window, null);
Assert.That(window.rootVisualElement.Query<FrameAnimationPreviewElement>().ToList().Count,
Is.GreaterThanOrEqualTo(1));
Assert.That(window.rootVisualElement.Query<Slider>().ToList().Count,
Is.GreaterThanOrEqualTo(2));
}
finally
{
UnityEngine.Object.DestroyImmediate(window);
}
}
private FrameClip Clip(string id, float speed, FrameClipEndBehavior end, params int[] durations)
{
var clip = Track(ScriptableObject.CreateInstance<FrameClip>());
clip.Configure(id, id, durations.Select((duration, index) =>
new FrameAnimationFrame(null, duration, $"frame_{index}", index)), speed, end);
return clip;
}
private FrameAnimationGraph Graph(
IEnumerable<FrameClip> clips,
IEnumerable<AnimationNode> nodes,
IEnumerable<AnimationEdge> edges,
IEnumerable<AnimationFlow> flows)
{
var graph = Track(ScriptableObject.CreateInstance<FrameAnimationGraph>());
graph.Configure("Graph", "Graph", clips, nodes, edges, flows, string.Empty);
return graph;
}
private T Track<T>(T value) where T : UnityEngine.Object
{
objects.Add(value);
return value;
}
}
}