Files
aibis-dream/Assets/Tests/FrameAnimation/EditMode/FrameAnimationImportTests.cs
T
2026-07-31 18:22:08 +08:00

438 lines
25 KiB
C#

using System;
using System.IO;
using System.Linq;
using System.Text;
using AibisDream.FrameAnimation.Editor;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
namespace AibisDream.FrameAnimation.Tests.EditMode
{
public sealed class FrameAnimationImportTests
{
private string TestRoot { get; set; }
private string TexturePath => TestRoot + "/Atlas.png";
private string JsonPath => TestRoot + "/Atlas.json";
private string GraphPath => TestRoot + "/ImportGraph.asset";
[SetUp]
public void SetUp()
{
TestRoot =
$"Assets/Tests/FrameAnimation/GeneratedImportTests_{Guid.NewGuid():N}";
EnsureFolder(TestRoot);
}
[TearDown]
public void TearDown()
{
if (!string.IsNullOrEmpty(TestRoot))
{
Assert.That(
AssetDatabase.DeleteAsset(TestRoot),
Is.True,
$"Failed to delete test asset folder: {TestRoot}");
}
AssetDatabase.Refresh();
}
[Test]
public void Parser_PreservesObjectAndArrayOrderAndUnicode()
{
Assert.That(AsepriteJsonParser.TryParse(ObjectJson(), "source", out var objectDocument, out var objectIssue),
Is.True, objectIssue?.Message);
Assert.That(AsepriteJsonParser.TryParse(ArrayJson(), "source", out var arrayDocument, out var arrayIssue),
Is.True, arrayIssue?.Message);
CollectionAssert.AreEqual(
new[] { "第二帧.png", "第一帧.png" },
objectDocument.Frames.Select(frame => frame.FrameName));
CollectionAssert.AreEqual(
objectDocument.Frames.Select(frame => frame.FrameName),
arrayDocument.Frames.Select(frame => frame.FrameName));
CollectionAssert.AreEqual(new[] { "待机", "眨眼" }, objectDocument.Tags.Select(tag => tag.Name));
var trimJson = "{" +
"\"frames\":{" +
"\"FrameA\":{\"frame\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"rotated\":false,\"trimmed\":false,\"duration\":100,\"spriteSourceSize\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"sourceSize\":{\"w\":2,\"h\":2}}}," +
"\"meta\":{\"image\":\"Atlas.png\",\"size\":{\"w\":2,\"h\":2},\"frameTags\":[" +
"{\"name\":\" 待机 \",\"from\":0,\"to\":0,\"direction\":\"forward\"}]}}";
Assert.That(AsepriteJsonParser.TryParse(trimJson, "source", out var trimDocument, out var trimIssue),
Is.True, trimIssue?.Message);
Assert.That(trimDocument.Tags.Single().Name, Is.EqualTo("待机"));
}
[Test]
public void Parser_GroupsSharedRectsWithoutCollapsingLogicalFrames()
{
Assert.That(AsepriteJsonParser.TryParse(
SharedRectJson(), "source", out var document, out var issue), Is.True, issue?.Message);
Assert.That(document.Frames.Count, Is.EqualTo(3));
Assert.That(document.SpriteSlots.Count, Is.EqualTo(2));
Assert.That(document.SpriteSlots[0].CanonicalFrameName, Is.EqualTo("FrameA"));
CollectionAssert.AreEqual(new[] { 0, 1 }, document.SpriteSlots[0].SourceIndices);
CollectionAssert.AreEqual(new[] { "FrameA", "FrameB" }, document.SpriteSlots[0].FrameNames);
Assert.That(document.SpriteSlotBySourceIndex[0], Is.SameAs(document.SpriteSlotBySourceIndex[1]));
Assert.That(document.SpriteSlotBySourceIndex[2], Is.Not.SameAs(document.SpriteSlotBySourceIndex[0]));
}
[TestCase("forward", new[] { 0, 1, 2, 3 })]
[TestCase("reverse", new[] { 3, 2, 1, 0 })]
[TestCase("pingpong", new[] { 0, 1, 2, 3, 2, 1 })]
[TestCase("pingpong_reverse", new[] { 3, 2, 1, 0, 1, 2 })]
public void TagExpansion_IsDeterministic(string direction, int[] expected)
{
var tag = new AsepriteSourceTag("测试", 0, 3, direction);
Assert.That(AsepriteJsonParser.TryExpandTag(tag, 4, "source", out var actual, out var issue),
Is.True, issue?.Message);
CollectionAssert.AreEqual(expected, actual);
}
[TestCase("pingpong")]
[TestCase("pingpong_reverse")]
public void TagExpansion_SingleFrameDoesNotDuplicateEndpoint(string direction)
{
var tag = new AsepriteSourceTag("单帧", 1, 1, direction);
Assert.That(AsepriteJsonParser.TryExpandTag(tag, 3, "source", out var actual, out _), Is.True);
CollectionAssert.AreEqual(new[] { 1 }, actual);
}
[Test]
public void WritableRefresh_CreatesAndUpdatesStableSubAssetsAndMissingState()
{
var graph = CreateWritableGraph(ObjectJson());
var source = graph.ImportSources.Single();
var firstPreview = FrameAnimationImportService.PreviewAll(graph);
Assert.That(firstPreview.HasErrors, Is.False, JoinIssues(firstPreview));
Assert.That(firstPreview.Sources.Single().SpriteDiffs.Count(diff =>
diff.Kind == FrameAnimationSpriteChangeKind.Added), Is.EqualTo(2));
Assert.That(FrameAnimationImportService.Apply(firstPreview, true, out var firstError), Is.True, firstError);
Assert.That(graph.Clips.Count, Is.EqualTo(2));
var idle = graph.Clips.Single(clip => clip.ImportInfo.SourceTagName == "待机");
Assert.That(AssetDatabase.IsSubAsset(idle), Is.True);
Assert.That(AssetDatabase.GetAssetPath(idle), Is.EqualTo(GraphPath));
Assert.That(idle.Frames[0].FrameName, Is.EqualTo("第二帧.png"));
Assert.That(source.LastSourceHash, Is.Not.Empty);
Assert.That(AssetDatabase.TryGetGUIDAndLocalFileIdentifier(idle, out _, out long firstLocalId), Is.True);
var idleSerialized = new SerializedObject(idle);
idleSerialized.FindProperty("id").stringValue = "用户重命名待机";
idleSerialized.FindProperty("displayName").stringValue = "自定义显示名";
idleSerialized.FindProperty("speed").floatValue = 1.75f;
idleSerialized.ApplyModifiedPropertiesWithoutUndo();
WriteJson(ObjectJson(duration: 180));
var updatePreview = FrameAnimationImportService.PreviewAll(graph);
Assert.That(updatePreview.HasErrors, Is.False, JoinIssues(updatePreview));
Assert.That(FrameAnimationImportService.Apply(updatePreview, true, out var updateError), Is.True, updateError);
Assert.That(AssetDatabase.TryGetGUIDAndLocalFileIdentifier(idle, out _, out long updatedLocalId), Is.True);
Assert.That(updatedLocalId, Is.EqualTo(firstLocalId));
Assert.That(idle.Id, Is.EqualTo("用户重命名待机"));
Assert.That(idle.DisplayName, Is.EqualTo("自定义显示名"));
Assert.That(idle.Speed, Is.EqualTo(1.75f));
Assert.That(idle.Frames[0].DurationMs, Is.EqualTo(180));
WriteJson(ObjectJson(includeIdle: false));
var missingPreview = FrameAnimationImportService.PreviewAll(graph);
Assert.That(FrameAnimationImportService.Apply(missingPreview, true, out var missingError), Is.True, missingError);
Assert.That(idle.ImportInfo.IsMissingFromSource, Is.True);
WriteJson(ObjectJson(duration: 220));
var restorePreview = FrameAnimationImportService.PreviewAll(graph);
Assert.That(FrameAnimationImportService.Apply(restorePreview, true, out var restoreError), Is.True, restoreError);
Assert.That(idle.ImportInfo.IsMissingFromSource, Is.False);
Assert.That(idle.Frames[0].DurationMs, Is.EqualTo(220));
Assert.That(AssetDatabase.TryGetGUIDAndLocalFileIdentifier(idle, out _, out long restoredLocalId), Is.True);
Assert.That(restoredLocalId, Is.EqualTo(firstLocalId));
}
[Test]
public void WritableRefresh_SharedRectsCreateOneSpriteAndPreserveLogicalFrames()
{
var graph = CreateWritableGraph(SharedRectJson());
var firstPreview = FrameAnimationImportService.PreviewAll(graph);
Assert.That(firstPreview.HasErrors, Is.False, JoinIssues(firstPreview));
var sourcePreview = firstPreview.Sources.Single();
Assert.That(sourcePreview.Document.Frames.Count, Is.EqualTo(3));
Assert.That(sourcePreview.Document.SpriteSlots.Count, Is.EqualTo(2));
Assert.That(sourcePreview.SpriteDiffs.Count(diff =>
diff.Kind == FrameAnimationSpriteChangeKind.Added), Is.EqualTo(2));
Assert.That(sourcePreview.SpriteDiffs.Single(diff => diff.SourceFrameCount == 2).FrameName,
Is.EqualTo("FrameA"));
Assert.That(FrameAnimationImportService.Apply(firstPreview, true, out var firstError),
Is.True, firstError);
var sprites = AssetDatabase.LoadAllAssetsAtPath(TexturePath).OfType<Sprite>().ToArray();
Assert.That(sprites.Length, Is.EqualTo(2));
var clip = graph.Clips.Single(item => item.ImportInfo.SourceTagName == "Shared");
Assert.That(clip.FrameCount, Is.EqualTo(3));
Assert.That(clip.Frames[0].Sprite, Is.SameAs(clip.Frames[1].Sprite));
Assert.That(clip.Frames[2].Sprite, Is.Not.SameAs(clip.Frames[0].Sprite));
CollectionAssert.AreEqual(new[] { "FrameA", "FrameB", "FrameC" },
clip.Frames.Select(frame => frame.FrameName));
CollectionAssert.AreEqual(new[] { 0, 1, 2 }, clip.Frames.Select(frame => frame.SourceIndex));
CollectionAssert.AreEqual(new[] { 100, 300, 80 }, clip.Frames.Select(frame => frame.DurationMs));
var firstIds = sprites.ToDictionary(sprite => sprite.name, GetLocalFileId);
var secondPreview = FrameAnimationImportService.PreviewAll(graph);
Assert.That(secondPreview.HasErrors, Is.False, JoinIssues(secondPreview));
Assert.That(secondPreview.Sources.Single().SpriteDiffs.All(diff =>
diff.Kind == FrameAnimationSpriteChangeKind.Unchanged), Is.True);
Assert.That(secondPreview.Sources.Single().ClipDiffs.All(diff =>
diff.Kind == FrameAnimationImportChangeKind.Unchanged), Is.True);
Assert.That(FrameAnimationImportService.Apply(secondPreview, true, out var secondError),
Is.True, secondError);
var secondIds = AssetDatabase.LoadAllAssetsAtPath(TexturePath).OfType<Sprite>()
.ToDictionary(sprite => sprite.name, GetLocalFileId);
CollectionAssert.AreEquivalent(firstIds, secondIds);
Assert.That(clip.Frames[0].Sprite, Is.SameAs(clip.Frames[1].Sprite));
}
[Test]
public void WritableRefresh_SharedSlotSplitAndMergeKeepDeterministicSpriteIdentity()
{
var graph = CreateWritableGraph(SharedPairJson(true));
var mergedPreview = FrameAnimationImportService.PreviewAll(graph);
Assert.That(FrameAnimationImportService.Apply(mergedPreview, true, out var mergedError),
Is.True, mergedError);
var canonical = AssetDatabase.LoadAllAssetsAtPath(TexturePath).OfType<Sprite>().Single();
var canonicalId = GetLocalFileId(canonical);
WriteJson(SharedPairJson(false));
var splitPreview = FrameAnimationImportService.PreviewAll(graph);
Assert.That(splitPreview.HasErrors, Is.False, JoinIssues(splitPreview));
Assert.That(splitPreview.Sources.Single().SpriteDiffs.Count(diff =>
diff.Kind == FrameAnimationSpriteChangeKind.Added), Is.EqualTo(1));
Assert.That(FrameAnimationImportService.Apply(splitPreview, true, out var splitError),
Is.True, splitError);
var splitSprites = AssetDatabase.LoadAllAssetsAtPath(TexturePath).OfType<Sprite>()
.ToDictionary(sprite => sprite.name);
Assert.That(splitSprites.Keys, Is.EquivalentTo(new[] { "FrameA", "FrameB" }));
Assert.That(GetLocalFileId(splitSprites["FrameA"]), Is.EqualTo(canonicalId));
var clip = graph.Clips.Single(item => item.ImportInfo.SourceTagName == "SharedPair");
Assert.That(clip.Frames[0].Sprite, Is.Not.SameAs(clip.Frames[1].Sprite));
WriteJson(SharedPairJson(true));
var mergedAgainPreview = FrameAnimationImportService.PreviewAll(graph);
Assert.That(mergedAgainPreview.HasErrors, Is.False, JoinIssues(mergedAgainPreview));
Assert.That(mergedAgainPreview.Sources.Single().SpriteDiffs.Any(diff =>
diff.Kind == FrameAnimationSpriteChangeKind.Retained && diff.FrameName == "FrameB"), Is.True);
Assert.That(FrameAnimationImportService.Apply(mergedAgainPreview, true, out var mergedAgainError),
Is.True, mergedAgainError);
Assert.That(GetLocalFileId(AssetDatabase.LoadAllAssetsAtPath(TexturePath).OfType<Sprite>()
.Single(sprite => sprite.name == "FrameA")), Is.EqualTo(canonicalId));
Assert.That(clip.Frames[0].Sprite, Is.SameAs(clip.Frames[1].Sprite));
}
[Test]
public void ReadOnlyPreview_PreservesImporterAndMapsSharedSprites()
{
var writableGraph = CreateWritableGraph(ObjectJson());
var writablePreview = FrameAnimationImportService.PreviewAll(writableGraph);
Assert.That(FrameAnimationImportService.Apply(writablePreview, true, out var objectApplyError),
Is.True, objectApplyError);
var importer = (TextureImporter)AssetImporter.GetAtPath(TexturePath);
var before = EditorJsonUtility.ToJson(importer);
var readOnlyGraph = ScriptableObject.CreateInstance<FrameAnimationGraph>();
readOnlyGraph.Configure("ReadOnly", "Read Only", Array.Empty<FrameClip>(),
Array.Empty<AnimationNode>(), Array.Empty<AnimationEdge>(), Array.Empty<AnimationFlow>(), string.Empty);
readOnlyGraph.AddImportSource(new FrameAnimationImportSource(
"Read Only", AssetDatabase.LoadAssetAtPath<Texture2D>(TexturePath),
AssetDatabase.LoadAssetAtPath<TextAsset>(JsonPath), new Vector2(0.5f, 0.5f), false));
AssetDatabase.CreateAsset(readOnlyGraph, TestRoot + "/ReadOnlyGraph.asset");
AssetDatabase.SaveAssets();
var objectPreview = FrameAnimationImportService.PreviewAll(readOnlyGraph);
Assert.That(objectPreview.HasErrors, Is.False, JoinIssues(objectPreview));
Assert.That(objectPreview.HasSpriteChanges, Is.False);
Assert.That(EditorJsonUtility.ToJson(importer), Is.EqualTo(before));
var sharedWritableGraph = CreateWritableGraph(SharedRectJson(), TestRoot + "/SharedImportGraph.asset");
var sharedPreview = FrameAnimationImportService.PreviewAll(sharedWritableGraph);
Assert.That(FrameAnimationImportService.Apply(sharedPreview, true, out var sharedApplyError),
Is.True, sharedApplyError);
before = EditorJsonUtility.ToJson(importer);
var readOnlySharedGraph = ScriptableObject.CreateInstance<FrameAnimationGraph>();
readOnlySharedGraph.Configure("ReadOnlyShared", "Read Only Shared", Array.Empty<FrameClip>(),
Array.Empty<AnimationNode>(), Array.Empty<AnimationEdge>(), Array.Empty<AnimationFlow>(), string.Empty);
readOnlySharedGraph.AddImportSource(new FrameAnimationImportSource(
"Read Only Shared", AssetDatabase.LoadAssetAtPath<Texture2D>(TexturePath),
AssetDatabase.LoadAssetAtPath<TextAsset>(JsonPath), new Vector2(0.5f, 0.5f), false));
AssetDatabase.CreateAsset(readOnlySharedGraph, TestRoot + "/ReadOnlySharedGraph.asset");
AssetDatabase.SaveAssets();
var sharedReadOnlyPreview = FrameAnimationImportService.PreviewAll(readOnlySharedGraph);
Assert.That(sharedReadOnlyPreview.HasErrors, Is.False, JoinIssues(sharedReadOnlyPreview));
Assert.That(sharedReadOnlyPreview.HasSpriteChanges, Is.False);
Assert.That(sharedReadOnlyPreview.Sources.Single().SpritePlan.ReadOnlySpritesBySourceIndex[0],
Is.SameAs(sharedReadOnlyPreview.Sources.Single().SpritePlan.ReadOnlySpritesBySourceIndex[1]));
Assert.That(FrameAnimationImportService.Apply(sharedReadOnlyPreview, false, out var error), Is.True, error);
var clip = readOnlySharedGraph.Clips.Single(item => item.ImportInfo.SourceTagName == "Shared");
Assert.That(clip.Frames[0].Sprite, Is.SameAs(clip.Frames[1].Sprite));
Assert.That(EditorJsonUtility.ToJson(importer), Is.EqualTo(before));
}
[Test]
public void ReadOnlyPreview_BlocksWhenSharedRectHasNoSprite()
{
CreateTexture();
WriteJson(SharedPairJson(true));
var graph = ScriptableObject.CreateInstance<FrameAnimationGraph>();
graph.Configure("MissingSprite", "Missing Sprite", Array.Empty<FrameClip>(),
Array.Empty<AnimationNode>(), Array.Empty<AnimationEdge>(), Array.Empty<AnimationFlow>(), string.Empty);
graph.AddImportSource(new FrameAnimationImportSource(
"Read Only", AssetDatabase.LoadAssetAtPath<Texture2D>(TexturePath),
AssetDatabase.LoadAssetAtPath<TextAsset>(JsonPath), new Vector2(0.5f, 0.5f), false));
AssetDatabase.CreateAsset(graph, TestRoot + "/MissingSpriteGraph.asset");
AssetDatabase.SaveAssets();
var preview = FrameAnimationImportService.PreviewAll(graph);
Assert.That(preview.HasErrors, Is.True);
Assert.That(preview.Sources.Single().Issues.Any(issue =>
issue.Code == FrameAnimationImportIssueCode.SpriteMatchFailed), Is.True);
Assert.That(FrameAnimationImportService.Apply(preview, false, out _), Is.False);
Assert.That(graph.Clips, Is.Empty);
}
[Test]
public void Preview_BlocksMultipleWritableOwnersWithoutMutation()
{
var graph = CreateWritableGraph(ObjectJson());
var source = graph.ImportSources.Single();
graph.AddImportSource(new FrameAnimationImportSource(
"Second Owner", source.Texture, source.AsepriteJson, source.Pivot, true));
EditorUtility.SetDirty(graph);
AssetDatabase.SaveAssets();
var importer = (TextureImporter)AssetImporter.GetAtPath(TexturePath);
var before = EditorJsonUtility.ToJson(importer);
var preview = FrameAnimationImportService.PreviewAll(graph);
Assert.That(preview.HasErrors, Is.True);
Assert.That(preview.Sources.SelectMany(item => item.Issues).Any(issue =>
issue.Code == FrameAnimationImportIssueCode.TextureOwnershipConflict), Is.True);
Assert.That(FrameAnimationImportService.Apply(preview, true, out _), Is.False);
Assert.That(EditorJsonUtility.ToJson(importer), Is.EqualTo(before));
Assert.That(graph.Clips, Is.Empty);
}
private FrameAnimationGraph CreateWritableGraph(string json, string graphAssetPath = null)
{
CreateTexture();
WriteJson(json);
var graph = ScriptableObject.CreateInstance<FrameAnimationGraph>();
graph.Configure("ImportGraph", "Import Graph", Array.Empty<FrameClip>(),
Array.Empty<AnimationNode>(), Array.Empty<AnimationEdge>(), Array.Empty<AnimationFlow>(), string.Empty);
graph.AddImportSource(new FrameAnimationImportSource(
"中文来源",
AssetDatabase.LoadAssetAtPath<Texture2D>(TexturePath),
AssetDatabase.LoadAssetAtPath<TextAsset>(JsonPath),
new Vector2(0.5f, 0.5f),
true));
AssetDatabase.CreateAsset(graph, graphAssetPath ?? GraphPath);
AssetDatabase.SaveAssets();
return graph;
}
private void CreateTexture()
{
var texture = new Texture2D(4, 2, TextureFormat.RGBA32, false);
var pixels = Enumerable.Range(0, 8)
.Select(index => index < 4 ? Color.red : Color.green)
.ToArray();
texture.SetPixels(pixels);
texture.Apply();
File.WriteAllBytes(TexturePath, texture.EncodeToPNG());
UnityEngine.Object.DestroyImmediate(texture);
AssetDatabase.ImportAsset(TexturePath, ImportAssetOptions.ForceSynchronousImport);
}
private void WriteJson(string json)
{
File.WriteAllText(JsonPath, json, new UTF8Encoding(false));
AssetDatabase.ImportAsset(JsonPath, ImportAssetOptions.ForceSynchronousImport);
}
private static string ObjectJson(int duration = 120, bool includeIdle = true)
{
var tags = includeIdle
? "{\"name\":\"待机\",\"from\":0,\"to\":0,\"direction\":\"forward\"}," +
"{\"name\":\"眨眼\",\"from\":0,\"to\":1,\"direction\":\"pingpong\"}"
: "{\"name\":\"眨眼\",\"from\":0,\"to\":1,\"direction\":\"pingpong\"}";
return "{" +
"\"frames\":{" +
$"\"第二帧.png\":{{\"frame\":{{\"x\":0,\"y\":0,\"w\":2,\"h\":2}},\"rotated\":false,\"trimmed\":false,\"duration\":{duration},\"spriteSourceSize\":{{\"x\":0,\"y\":0,\"w\":2,\"h\":2}},\"sourceSize\":{{\"w\":2,\"h\":2}}}}," +
"\"第一帧.png\":{\"frame\":{\"x\":2,\"y\":0,\"w\":2,\"h\":2},\"rotated\":false,\"trimmed\":false,\"duration\":80,\"spriteSourceSize\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"sourceSize\":{\"w\":2,\"h\":2}}}," +
$"\"meta\":{{\"image\":\"Atlas.png\",\"size\":{{\"w\":4,\"h\":2}},\"frameTags\":[{tags}]}}" +
"}";
}
private static string ArrayJson()
{
return "{" +
"\"frames\":[" +
"{\"filename\":\"第二帧.png\",\"frame\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"rotated\":false,\"trimmed\":false,\"duration\":120,\"spriteSourceSize\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"sourceSize\":{\"w\":2,\"h\":2}}," +
"{\"filename\":\"第一帧.png\",\"frame\":{\"x\":2,\"y\":0,\"w\":2,\"h\":2},\"rotated\":false,\"trimmed\":false,\"duration\":80,\"spriteSourceSize\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"sourceSize\":{\"w\":2,\"h\":2}}]," +
"\"meta\":{\"image\":\"Atlas.png\",\"size\":{\"w\":4,\"h\":2},\"frameTags\":[" +
"{\"name\":\"待机\",\"from\":0,\"to\":0,\"direction\":\"forward\"}," +
"{\"name\":\"眨眼\",\"from\":0,\"to\":1,\"direction\":\"pingpong\"}]}}";
}
private static string SharedRectJson()
{
return "{" +
"\"frames\":{" +
"\"FrameA\":{\"frame\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"rotated\":false,\"trimmed\":false,\"duration\":100,\"spriteSourceSize\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"sourceSize\":{\"w\":2,\"h\":2}}," +
"\"FrameB\":{\"frame\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"rotated\":false,\"trimmed\":false,\"duration\":300,\"spriteSourceSize\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"sourceSize\":{\"w\":2,\"h\":2}}," +
"\"FrameC\":{\"frame\":{\"x\":2,\"y\":0,\"w\":2,\"h\":2},\"rotated\":false,\"trimmed\":false,\"duration\":80,\"spriteSourceSize\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"sourceSize\":{\"w\":2,\"h\":2}}}," +
"\"meta\":{\"image\":\"Atlas.png\",\"size\":{\"w\":4,\"h\":2},\"frameTags\":[" +
"{\"name\":\"Shared\",\"from\":0,\"to\":2,\"direction\":\"forward\"}]}}";
}
private static string SharedPairJson(bool merged)
{
var secondX = merged ? 0 : 2;
return "{" +
"\"frames\":{" +
"\"FrameA\":{\"frame\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"rotated\":false,\"trimmed\":false,\"duration\":100,\"spriteSourceSize\":{\"x\":0,\"y\":0,\"w\":2,\"h\":2},\"sourceSize\":{\"w\":2,\"h\":2}}," +
$"\"FrameB\":{{\"frame\":{{\"x\":{secondX},\"y\":0,\"w\":2,\"h\":2}},\"rotated\":false,\"trimmed\":false,\"duration\":300,\"spriteSourceSize\":{{\"x\":0,\"y\":0,\"w\":2,\"h\":2}},\"sourceSize\":{{\"w\":2,\"h\":2}}}}}}," +
"\"meta\":{\"image\":\"Atlas.png\",\"size\":{\"w\":4,\"h\":2},\"frameTags\":[" +
"{\"name\":\"SharedPair\",\"from\":0,\"to\":1,\"direction\":\"forward\"}]}}";
}
private static long GetLocalFileId(Sprite sprite)
{
Assert.That(AssetDatabase.TryGetGUIDAndLocalFileIdentifier(sprite, out _, out long localId), Is.True);
return localId;
}
private static string JoinIssues(FrameAnimationImportPreview preview)
{
return string.Join("\n", preview.Sources.SelectMany(source => source.Issues).Select(issue => issue.Message));
}
private static void EnsureFolder(string path)
{
var segments = path.Split('/');
var current = segments[0];
for (var index = 1; index < segments.Length; index++)
{
var next = current + "/" + segments[index];
if (!AssetDatabase.IsValidFolder(next))
{
AssetDatabase.CreateFolder(current, segments[index]);
}
current = next;
}
}
}
}