Files
aibis-dream/Assets/Tests/FrameAnimation/PlayMode/FrameAnimationPlayerTests.cs
T

450 lines
19 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using UnityEngine.UI;
namespace AibisDream.FrameAnimation.Tests.PlayMode
{
public sealed class FrameAnimationPlayerTests
{
private readonly List<UnityEngine.Object> createdObjects = new List<UnityEngine.Object>();
[TearDown]
public void TearDown()
{
foreach (var createdObject in createdObjects)
{
if (createdObject != null)
{
UnityEngine.Object.DestroyImmediate(createdObject);
}
}
createdObjects.Clear();
}
[Test]
public void SpriteRenderer_PlayerShowsFirstFrameAndCompletesWithClear()
{
var first = CreateSprite(Color.red);
var second = CreateSprite(Color.blue);
var clip = CreateClip("Clip", FrameClipEndBehavior.Clear, first, second);
var graph = CreateDirectGraph(clip);
var gameObject = Track(new GameObject("Sprite Player"));
var renderer = gameObject.AddComponent<SpriteRenderer>();
var player = gameObject.AddComponent<FrameAnimationPlayer>();
player.ConfigureForAuthoring(graph, false, 1f);
var handle = player.Play(clip.Id);
Assert.That(renderer.sprite, Is.EqualTo(first));
player.EvaluateForTests(0.2d);
Assert.That(handle.IsCompleted, Is.True);
Assert.That(handle.Result.Reason, Is.EqualTo(FrameAnimationCompletionReason.Completed));
Assert.That(renderer.sprite, Is.Null);
Assert.That(renderer.enabled, Is.True);
}
[Test]
public void Image_PlayerSupportsHideAndNextPlayReEnablesTarget()
{
var sprite = CreateSprite(Color.green);
var clip = CreateClip("Clip", FrameClipEndBehavior.HideTarget, sprite);
var graph = CreateDirectGraph(clip);
var gameObject = Track(new GameObject("Image Player", typeof(RectTransform), typeof(CanvasRenderer)));
var image = gameObject.AddComponent<Image>();
var player = gameObject.AddComponent<FrameAnimationPlayer>();
player.ConfigureForAuthoring(graph, false, 1f);
player.Play(clip.Id);
player.EvaluateForTests(0.1d);
Assert.That(image.enabled, Is.False);
player.Play(clip.Id, new FrameAnimationPlayOptions(FrameClipEndBehavior.HoldLastFrame));
Assert.That(image.enabled, Is.True);
Assert.That(image.sprite, Is.EqualTo(sprite));
}
[Test]
public void FailedPlayDoesNotReplaceActiveRequest()
{
var clip = CreateClip("Loop", FrameClipEndBehavior.Loop, CreateSprite(Color.white));
var graph = CreateDirectGraph(clip);
var gameObject = Track(new GameObject("Player"));
gameObject.AddComponent<SpriteRenderer>();
var player = gameObject.AddComponent<FrameAnimationPlayer>();
player.ConfigureForAuthoring(graph, false, 1f);
var active = player.Play(clip.Id);
var failed = player.Play("Missing");
Assert.That(failed.Result.Reason, Is.EqualTo(FrameAnimationCompletionReason.Failed));
Assert.That(failed.Result.Error.Code, Is.EqualTo(FrameAnimationPlaybackErrorCode.PlayableNotFound));
Assert.That(active.IsCompleted, Is.False);
Assert.That(player.State, Is.EqualTo(FrameAnimationPlaybackState.Playing));
Assert.That(player.CurrentPlayableId, Is.EqualTo(clip.Id));
}
[Test]
public void SuccessfulPlayReplacesPreviousRequestExactlyOnce()
{
var first = CreateClip("First", FrameClipEndBehavior.Loop, CreateSprite(Color.white));
var second = CreateClip("Second", FrameClipEndBehavior.Loop, CreateSprite(Color.black));
var graph = Track(ScriptableObject.CreateInstance<FrameAnimationGraph>());
graph.Configure(
"Graph",
"Graph",
new[] { first, second },
Array.Empty<AnimationNode>(),
Array.Empty<AnimationEdge>(),
Array.Empty<AnimationFlow>(),
first.Id);
var gameObject = Track(new GameObject("Replace"));
gameObject.AddComponent<SpriteRenderer>();
var player = gameObject.AddComponent<FrameAnimationPlayer>();
player.ConfigureForAuthoring(graph, false, 1f);
var firstHandle = player.Play(first.Id);
var secondHandle = player.Play(second.Id);
Assert.That(firstHandle.Result.Reason, Is.EqualTo(FrameAnimationCompletionReason.Replaced));
Assert.That(secondHandle.IsCompleted, Is.False);
Assert.That(player.CurrentPlayableId, Is.EqualTo(second.Id));
}
[Test]
public void FailedTargetProbeDoesNotDiscardCurrentTargetOrRequest()
{
var clip = CreateClip("Loop", FrameClipEndBehavior.Loop, CreateSprite(Color.white));
var graph = CreateDirectGraph(clip);
var gameObject = Track(new GameObject("Player", typeof(RectTransform), typeof(CanvasRenderer)));
gameObject.AddComponent<SpriteRenderer>();
var player = gameObject.AddComponent<FrameAnimationPlayer>();
player.ConfigureForAuthoring(graph, false, 1f);
var active = player.Play(clip.Id);
gameObject.AddComponent<Image>();
var failed = player.Play(clip.Id);
Assert.That(failed.Result.Error.Code, Is.EqualTo(FrameAnimationPlaybackErrorCode.TargetConflict));
Assert.That(active.IsCompleted, Is.False);
Assert.That(player.State, Is.EqualTo(FrameAnimationPlaybackState.Playing));
player.EvaluateForTests(0.05d);
Assert.That(active.IsCompleted, Is.False);
}
[Test]
public void ZeroSpeedKeepsRequestPlayingWithoutAdvancing()
{
var first = CreateSprite(Color.red);
var second = CreateSprite(Color.blue);
var clip = CreateClip("Clip", FrameClipEndBehavior.HoldLastFrame, first, second);
var graph = CreateDirectGraph(clip);
var gameObject = Track(new GameObject("Zero Speed"));
var renderer = gameObject.AddComponent<SpriteRenderer>();
var player = gameObject.AddComponent<FrameAnimationPlayer>();
player.ConfigureForAuthoring(graph, false, 0f);
var handle = player.Play(clip.Id);
player.EvaluateForTests(10d);
Assert.That(handle.IsCompleted, Is.False);
Assert.That(player.State, Is.EqualTo(FrameAnimationPlaybackState.Playing));
Assert.That(player.CurrentFrameIndex, Is.EqualTo(0));
Assert.That(renderer.sprite, Is.EqualTo(first));
}
[Test]
public void PauseAndResumePreserveFrameProgress()
{
var clip = CreateClip(
"Clip",
FrameClipEndBehavior.HoldLastFrame,
CreateSprite(Color.red),
CreateSprite(Color.blue));
var graph = CreateDirectGraph(clip);
var gameObject = Track(new GameObject("Pause"));
gameObject.AddComponent<SpriteRenderer>();
var player = gameObject.AddComponent<FrameAnimationPlayer>();
player.ConfigureForAuthoring(graph, false, 1f);
player.Play(clip.Id);
player.EvaluateForTests(0.05d);
player.Pause();
player.EvaluateForTests(1d);
Assert.That(player.CurrentFrameIndex, Is.EqualTo(0));
player.Resume();
player.EvaluateForTests(0.05d);
Assert.That(player.CurrentFrameIndex, Is.EqualTo(1));
Assert.That(player.State, Is.EqualTo(FrameAnimationPlaybackState.Playing));
}
[Test]
public void TargetConflictReturnsStructuredFailure()
{
var clip = CreateClip("Clip", FrameClipEndBehavior.HoldLastFrame, CreateSprite(Color.white));
var graph = CreateDirectGraph(clip);
var gameObject = Track(new GameObject("Conflict", typeof(RectTransform), typeof(CanvasRenderer)));
gameObject.AddComponent<SpriteRenderer>();
gameObject.AddComponent<Image>();
var player = gameObject.AddComponent<FrameAnimationPlayer>();
player.ConfigureForAuthoring(graph, false, 1f);
var handle = player.Play(clip.Id);
Assert.That(handle.Result.Reason, Is.EqualTo(FrameAnimationCompletionReason.Failed));
Assert.That(handle.Result.Error.Code, Is.EqualTo(FrameAnimationPlaybackErrorCode.TargetConflict));
}
[Test]
public void StopAppliesVisualModeWithoutActiveRequest()
{
var gameObject = Track(new GameObject("Stop"));
var renderer = gameObject.AddComponent<SpriteRenderer>();
renderer.sprite = CreateSprite(Color.yellow);
var player = gameObject.AddComponent<FrameAnimationPlayer>();
player.Stop(FrameAnimationStopMode.Clear);
Assert.That(renderer.sprite, Is.Null);
Assert.That(renderer.enabled, Is.True);
}
[Test]
public void TryBindGraph_AllowsInitialBindAndRejectsReplacementAfterPlayback()
{
var firstGraph = CreateDirectGraph(
CreateClip("First", FrameClipEndBehavior.Loop, CreateSprite(Color.red)));
var secondGraph = CreateDirectGraph(
CreateClip("Second", FrameClipEndBehavior.Loop, CreateSprite(Color.blue)));
var gameObject = Track(new GameObject("Runtime Bind"));
gameObject.AddComponent<SpriteRenderer>();
var player = gameObject.AddComponent<FrameAnimationPlayer>();
Assert.That(player.TryBindGraph(firstGraph, out var bindError), Is.True);
Assert.That(bindError.Code, Is.EqualTo(FrameAnimationPlaybackErrorCode.None));
player.Play("First");
Assert.That(player.TryBindGraph(secondGraph, out var replaceError), Is.False);
Assert.That(
replaceError.Code,
Is.EqualTo(FrameAnimationPlaybackErrorCode.GraphBindingNotAllowed));
Assert.That(player.Graph, Is.SameAs(firstGraph));
}
[Test]
public void RestoreTerminalState_HoldLastFrameAppliesLastFrameImmediately()
{
var first = CreateSprite(Color.red);
var last = CreateSprite(Color.blue);
var clip = CreateClip("Terminal", FrameClipEndBehavior.HoldLastFrame, first, last);
var graph = CreateDirectGraph(clip);
var gameObject = Track(new GameObject("Restore Terminal"));
var renderer = gameObject.AddComponent<SpriteRenderer>();
var player = gameObject.AddComponent<FrameAnimationPlayer>();
player.ConfigureForAuthoring(graph, false, 1f);
var handle = player.RestoreTerminalState(clip.Id);
Assert.That(handle.IsCompleted, Is.True);
Assert.That(handle.Result.Reason, Is.EqualTo(FrameAnimationCompletionReason.Completed));
Assert.That(player.State, Is.EqualTo(FrameAnimationPlaybackState.Stopped));
Assert.That(renderer.sprite, Is.SameAs(last));
Assert.That(renderer.enabled, Is.True);
}
[Test]
public void RestoreTerminalState_AppliesClearAndHideTargetEndBehaviors()
{
var clearClip = CreateClip(
"Clear",
FrameClipEndBehavior.Clear,
CreateSprite(Color.red));
var hideClip = CreateClip(
"Hide",
FrameClipEndBehavior.HideTarget,
CreateSprite(Color.blue));
var graph = Track(ScriptableObject.CreateInstance<FrameAnimationGraph>());
graph.Configure(
"Graph",
"Graph",
new[] { clearClip, hideClip },
Array.Empty<AnimationNode>(),
Array.Empty<AnimationEdge>(),
Array.Empty<AnimationFlow>(),
clearClip.Id);
var gameObject = Track(new GameObject("Restore End Behaviors"));
var renderer = gameObject.AddComponent<SpriteRenderer>();
var player = gameObject.AddComponent<FrameAnimationPlayer>();
player.ConfigureForAuthoring(graph, false, 1f);
player.RestoreTerminalState(clearClip.Id);
Assert.That(renderer.sprite, Is.Null);
Assert.That(renderer.enabled, Is.True);
player.RestoreTerminalState(hideClip.Id);
Assert.That(renderer.enabled, Is.False);
}
[Test]
public void RestoreTerminalState_LoopRestartsFromFirstFrameWithoutCompleting()
{
var first = CreateSprite(Color.red);
var second = CreateSprite(Color.blue);
var clip = CreateClip("Loop", FrameClipEndBehavior.Loop, first, second);
var graph = CreateDirectGraph(clip);
var gameObject = Track(new GameObject("Restore Loop"));
var renderer = gameObject.AddComponent<SpriteRenderer>();
var player = gameObject.AddComponent<FrameAnimationPlayer>();
player.ConfigureForAuthoring(graph, false, 1f);
var handle = player.RestoreTerminalState(clip.Id);
Assert.That(handle.IsCompleted, Is.False);
Assert.That(player.State, Is.EqualTo(FrameAnimationPlaybackState.Playing));
Assert.That(player.CurrentFrameIndex, Is.EqualTo(0));
Assert.That(renderer.sprite, Is.SameAs(first));
}
[Test]
public void FailedTerminalRestoreDoesNotReplaceActiveRequest()
{
var clip = CreateClip("Loop", FrameClipEndBehavior.Loop, CreateSprite(Color.white));
var graph = CreateDirectGraph(clip);
var gameObject = Track(new GameObject("Failed Restore"));
gameObject.AddComponent<SpriteRenderer>();
var player = gameObject.AddComponent<FrameAnimationPlayer>();
player.ConfigureForAuthoring(graph, false, 1f);
var active = player.Play(clip.Id);
var failed = player.RestoreTerminalState("Missing");
Assert.That(failed.IsCompleted, Is.True);
Assert.That(failed.Result.Reason, Is.EqualTo(FrameAnimationCompletionReason.Failed));
Assert.That(
failed.Result.Error.Code,
Is.EqualTo(FrameAnimationPlaybackErrorCode.PlayableNotFound));
Assert.That(active.IsCompleted, Is.False);
Assert.That(player.State, Is.EqualTo(FrameAnimationPlaybackState.Playing));
Assert.That(player.CurrentPlayableId, Is.EqualTo(clip.Id));
}
[Test]
public void RestoreTerminalState_FlowUsesTerminalStepEndState()
{
var firstClip = CreateClip(
"First",
FrameClipEndBehavior.HoldLastFrame,
CreateSprite(Color.red));
var terminalSprite = CreateSprite(Color.green);
var terminalClip = CreateClip(
"Terminal",
FrameClipEndBehavior.HoldLastFrame,
CreateSprite(Color.blue),
terminalSprite);
var firstNode = new AnimationNode(firstClip.Id);
var terminalNode = new AnimationNode(terminalClip.Id);
var edge = new AnimationEdge(firstNode.InternalId, terminalNode.InternalId);
var flow = new AnimationFlow("Flow", "Flow", firstNode.InternalId);
var graph = Track(ScriptableObject.CreateInstance<FrameAnimationGraph>());
graph.Configure(
"Graph",
"Graph",
new[] { firstClip, terminalClip },
new[] { firstNode, terminalNode },
new[] { edge },
new[] { flow },
flow.Id);
var gameObject = Track(new GameObject("Restore Flow"));
var renderer = gameObject.AddComponent<SpriteRenderer>();
var player = gameObject.AddComponent<FrameAnimationPlayer>();
player.ConfigureForAuthoring(graph, false, 1f);
var handle = player.RestoreTerminalState(flow.Id);
Assert.That(handle.IsCompleted, Is.True);
Assert.That(renderer.sprite, Is.SameAs(terminalSprite));
}
[UnityTest]
public IEnumerator OnDisableSettlesActiveRequestAsStopped()
{
var clip = CreateClip("Loop", FrameClipEndBehavior.Loop, CreateSprite(Color.white));
var graph = CreateDirectGraph(clip);
var gameObject = Track(new GameObject("Disable"));
gameObject.AddComponent<SpriteRenderer>();
var player = gameObject.AddComponent<FrameAnimationPlayer>();
player.ConfigureForAuthoring(graph, false, 1f);
var handle = player.Play(clip.Id);
player.enabled = false;
yield return null;
Assert.That(handle.Result.Reason, Is.EqualTo(FrameAnimationCompletionReason.Stopped));
Assert.That(player.State, Is.EqualTo(FrameAnimationPlaybackState.Stopped));
}
[UnityTest]
public IEnumerator PlayOnEnableStartsDefaultPlayableFromBeginning()
{
var clip = CreateClip("Loop", FrameClipEndBehavior.Loop, CreateSprite(Color.cyan));
var graph = CreateDirectGraph(clip);
var gameObject = Track(new GameObject("Auto Play"));
gameObject.SetActive(false);
gameObject.AddComponent<SpriteRenderer>();
var player = gameObject.AddComponent<FrameAnimationPlayer>();
player.ConfigureForAuthoring(graph, true, 1f);
gameObject.SetActive(true);
yield return null;
Assert.That(player.State, Is.EqualTo(FrameAnimationPlaybackState.Playing));
Assert.That(player.CurrentPlayableId, Is.EqualTo(clip.Id));
Assert.That(player.CurrentFrameIndex, Is.EqualTo(0));
}
private FrameClip CreateClip(string id, FrameClipEndBehavior endBehavior, params Sprite[] sprites)
{
var clip = Track(ScriptableObject.CreateInstance<FrameClip>());
var frames = new List<FrameAnimationFrame>();
for (var index = 0; index < sprites.Length; index++)
{
frames.Add(new FrameAnimationFrame(sprites[index], 100, $"frame_{index}", index));
}
clip.Configure(id, id, frames, 1f, endBehavior);
return clip;
}
private FrameAnimationGraph CreateDirectGraph(FrameClip clip)
{
var graph = Track(ScriptableObject.CreateInstance<FrameAnimationGraph>());
graph.Configure(
"Graph",
"Graph",
new[] { clip },
Array.Empty<AnimationNode>(),
Array.Empty<AnimationEdge>(),
Array.Empty<AnimationFlow>(),
clip.Id);
return graph;
}
private Sprite CreateSprite(Color color)
{
var texture = Track(new Texture2D(2, 2));
texture.SetPixels(new[] { color, color, color, color });
texture.Apply();
return Track(Sprite.Create(texture, new Rect(0, 0, 2, 2), new Vector2(0.5f, 0.5f)));
}
private T Track<T>(T value) where T : UnityEngine.Object
{
createdObjects.Add(value);
return value;
}
}
}