using System; using System.Linq; using UnityEditor; using UnityEngine; namespace AibisDream.FrameAnimation.Editor { public static class FrameAnimationPreviewSampleBuilder { private const string Folder = "Assets/GameContent/Test/FrameAnimation/Preview"; private const string GraphPath = Folder + "/PreviewSampleGraph.asset"; private const string TexturePath = "Assets/GameContent/Test/AnimatorRaw/手接树叶特写.png"; [MenuItem("Tools/Frame Animation/Create Phase 5 Preview Sample")] public static void BuildFromCommandLine() { if (AssetDatabase.LoadAssetAtPath(GraphPath) != null) { Debug.Log($"Frame Animation preview sample already exists; preserved without overwrite: {GraphPath}"); return; } EnsureFolder(Folder); var sprites = AssetDatabase.LoadAllAssetsAtPath(TexturePath).OfType() .OrderBy(sprite => sprite.name, StringComparer.Ordinal).ToArray(); if (sprites.Length < 6) { throw new InvalidOperationException("第五阶段预览样例需要至少 6 个已切分 Sprite。"); } var graph = ScriptableObject.CreateInstance(); graph.name = "PreviewSampleGraph"; AssetDatabase.CreateAsset(graph, GraphPath); var action = CreateClip(graph, "PreviewAction", "Preview Action", FrameClipEndBehavior.HoldLastFrame, new[] { new FrameAnimationFrame(sprites[0], 100, sprites[0].name, 0), new FrameAnimationFrame(sprites[1], 160, sprites[1].name, 1), new FrameAnimationFrame(sprites[2], 220, sprites[2].name, 2) }); var withEmpty = CreateClip(graph, "PreviewEmpty", "Preview Empty Frame", FrameClipEndBehavior.HoldLastFrame, new[] { new FrameAnimationFrame(sprites[3], 120, sprites[3].name, 3), new FrameAnimationFrame(null, 300, "empty", -1), new FrameAnimationFrame(sprites[4], 120, sprites[4].name, 4) }); var idle = CreateClip(graph, "PreviewIdle", "Preview Idle Loop", FrameClipEndBehavior.Loop, new[] { new FrameAnimationFrame(sprites[4], 140, sprites[4].name, 4), new FrameAnimationFrame(sprites[5], 140, sprites[5].name, 5) }); var finiteA = new AnimationNode(action.Id, "Finite Action", speedOverride: 2f, internalId: "51000000000000000000000000000001"); var finiteB = new AnimationNode(withEmpty.Id, "Finite Empty", endBehaviorOverride: FrameClipEndBehavior.HoldLastFrame, internalId: "51000000000000000000000000000002"); var loopA = new AnimationNode(action.Id, "Loop Intro", internalId: "51000000000000000000000000000003"); var loopB = new AnimationNode(idle.Id, "Loop Terminal", endBehaviorOverride: FrameClipEndBehavior.Loop, internalId: "51000000000000000000000000000004"); var blocked = new AnimationNode(withEmpty.Id, "Zero Speed Block", speedOverride: 0f, internalId: "51000000000000000000000000000005"); var clear = new AnimationNode(action.Id, "Clear Terminal", endBehaviorOverride: FrameClipEndBehavior.Clear, internalId: "51000000000000000000000000000006"); var hidden = new AnimationNode(idle.Id, "Hide Terminal", endBehaviorOverride: FrameClipEndBehavior.HideTarget, internalId: "51000000000000000000000000000007"); var finiteFlow = new AnimationFlow("PreviewFiniteFlow", "Finite Flow", finiteA.InternalId); var loopFlow = new AnimationFlow("PreviewLoopFlow", "Loop Flow", loopA.InternalId); var blockedFlow = new AnimationFlow("PreviewBlockedFlow", "Zero Speed Flow", blocked.InternalId); var clearFlow = new AnimationFlow("PreviewClearFlow", "Clear Flow", clear.InternalId); var hideFlow = new AnimationFlow("PreviewHideFlow", "Hide Flow", hidden.InternalId); graph.Configure( "PreviewSampleGraph", "Phase 5 Preview Sample", new[] { action, withEmpty, idle }, new[] { finiteA, finiteB, loopA, loopB, blocked, clear, hidden }, new[] { new AnimationEdge(finiteA.InternalId, finiteB.InternalId, "52000000000000000000000000000001"), new AnimationEdge(loopA.InternalId, loopB.InternalId, "52000000000000000000000000000002") }, new[] { finiteFlow, loopFlow, blockedFlow, clearFlow, hideFlow }, finiteFlow.Id); var positions = new[] { (finiteA, new Vector2(80f, 80f)), (finiteB, new Vector2(420f, 80f)), (loopA, new Vector2(80f, 320f)), (loopB, new Vector2(420f, 320f)), (blocked, new Vector2(80f, 560f)), (clear, new Vector2(420f, 560f)), (hidden, new Vector2(760f, 560f)) }; foreach (var item in positions) { graph.EditorData.GetOrCreateNodeData(item.Item1.InternalId, item.Item2); } var colors = new[] { new Color(0.25f, 0.65f, 0.95f), new Color(0.55f, 0.4f, 0.9f), new Color(0.9f, 0.55f, 0.2f), new Color(0.3f, 0.75f, 0.5f), new Color(0.85f, 0.35f, 0.45f) }; var flows = graph.Flows.ToArray(); for (var index = 0; index < flows.Length; index++) { graph.EditorData.GetOrCreateFlowData(flows[index].Id, colors[index]); } EditorUtility.SetDirty(graph); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); Debug.Log($"Frame Animation phase 5 preview sample created: {GraphPath}"); } private static FrameClip CreateClip( FrameAnimationGraph graph, string id, string displayName, FrameClipEndBehavior endBehavior, FrameAnimationFrame[] frames) { var clip = ScriptableObject.CreateInstance(); clip.name = id; clip.Configure(id, displayName, frames, 1f, endBehavior); AssetDatabase.AddObjectToAsset(clip, graph); return clip; } 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; } } } }