Files
aibis-dream/Assets/Editor/FrameAnimation/FrameAnimationRuntimeSampleBuilder.cs
T

224 lines
8.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
namespace AibisDream.FrameAnimation.Editor
{
public static class FrameAnimationRuntimeSampleBuilder
{
private const string SourceTexturePath =
"Assets/GameContent/Test/AnimatorRaw/手接树叶特写.png";
private const string SampleFolder = "Assets/GameContent/Test/FrameAnimation";
private const string IntroClipPath = SampleFolder + "/Intro.asset";
private const string IdleClipPath = SampleFolder + "/Idle.asset";
private const string FlowGraphPath = SampleFolder + "/FlowSampleGraph.asset";
private const string DirectGraphPath = SampleFolder + "/DirectSampleGraph.asset";
private const string ScenePath = "Assets/Scenes/FrameAnimationRuntimeTest.unity";
[MenuItem("Tools/Frame Animation/Rebuild Runtime Sample")]
private static void BuildFromMenu()
{
if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
{
return;
}
BuildFromCommandLine();
}
public static void BuildFromCommandLine()
{
EnsureFolder(SampleFolder);
var sprites = AssetDatabase.LoadAllAssetsAtPath(SourceTexturePath)
.OfType<Sprite>()
.OrderBy(sprite => ExtractFrameNumber(sprite.name))
.ToArray();
if (sprites.Length < 21)
{
throw new InvalidOperationException(
$"样例源 Texture 需要至少 21 个 Sprite,当前只找到 {sprites.Length} 个。");
}
var introClip = CreateOrLoadAsset<FrameClip>(IntroClipPath);
introClip.Configure(
"Intro",
"Intro",
CreateFrames(sprites, 0, 5),
1f,
FrameClipEndBehavior.HoldLastFrame);
EditorUtility.SetDirty(introClip);
var idleClip = CreateOrLoadAsset<FrameClip>(IdleClipPath);
idleClip.Configure(
"Idle",
"Idle",
CreateFrames(sprites, 5, 16),
1f,
FrameClipEndBehavior.Loop);
EditorUtility.SetDirty(idleClip);
var flowGraph = CreateOrLoadAsset<FrameAnimationGraph>(FlowGraphPath);
var introNodeId = FindExistingNodeId(flowGraph, introClip.Id);
var idleNodeId = FindExistingNodeId(flowGraph, idleClip.Id);
var introNode = new AnimationNode(introClip.Id, "Intro", internalId: introNodeId);
var idleNode = new AnimationNode(
idleClip.Id,
"Idle Loop",
FrameClipEndBehavior.Loop,
internalId: idleNodeId);
var flow = new AnimationFlow("IntroToIdle", "Intro To Idle", introNode.InternalId);
flowGraph.Configure(
"FlowSampleGraph",
"Flow Sample Graph",
new[] { introClip, idleClip },
new[] { introNode, idleNode },
new[] { new AnimationEdge(introNode.InternalId, idleNode.InternalId) },
new[] { flow },
flow.Id);
EditorUtility.SetDirty(flowGraph);
var directGraph = CreateOrLoadAsset<FrameAnimationGraph>(DirectGraphPath);
directGraph.Configure(
"DirectSampleGraph",
"Direct Sample Graph",
new[] { introClip, idleClip },
Array.Empty<AnimationNode>(),
Array.Empty<AnimationEdge>(),
Array.Empty<AnimationFlow>(),
idleClip.Id);
EditorUtility.SetDirty(directGraph);
AssetDatabase.SaveAssets();
BuildScene(flowGraph, directGraph);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log($"Frame Animation runtime sample rebuilt: {ScenePath}");
}
private static IEnumerable<FrameAnimationFrame> CreateFrames(
IReadOnlyList<Sprite> sprites,
int startIndex,
int count)
{
for (var offset = 0; offset < count; offset++)
{
var sourceIndex = startIndex + offset;
var sprite = sprites[sourceIndex];
yield return new FrameAnimationFrame(sprite, 250, sprite.name, sourceIndex);
}
}
private static void BuildScene(
FrameAnimationGraph flowGraph,
FrameAnimationGraph directGraph)
{
var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
var cameraObject = new GameObject("Main Camera", typeof(Camera));
cameraObject.tag = "MainCamera";
cameraObject.transform.position = new Vector3(0f, 0f, -10f);
var camera = cameraObject.GetComponent<Camera>();
camera.orthographic = true;
camera.orthographicSize = 6f;
camera.clearFlags = CameraClearFlags.SolidColor;
camera.backgroundColor = new Color(0.08f, 0.09f, 0.12f, 1f);
var spriteObject = new GameObject("Flow Sample - SpriteRenderer", typeof(SpriteRenderer));
spriteObject.transform.position = new Vector3(-3.5f, 0f, 0f);
spriteObject.transform.localScale = Vector3.one * 0.45f;
var spritePlayer = spriteObject.AddComponent<FrameAnimationPlayer>();
spritePlayer.ConfigureForAuthoring(flowGraph, true, 1f);
var canvasObject = new GameObject(
"Direct Clip Sample Canvas",
typeof(Canvas),
typeof(CanvasScaler),
typeof(GraphicRaycaster));
var canvas = canvasObject.GetComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
var scaler = canvasObject.GetComponent<CanvasScaler>();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(1280f, 720f);
var imageObject = new GameObject(
"Direct Idle - Image",
typeof(RectTransform),
typeof(CanvasRenderer),
typeof(Image));
imageObject.transform.SetParent(canvasObject.transform, false);
var rectTransform = imageObject.GetComponent<RectTransform>();
rectTransform.sizeDelta = new Vector2(384f, 216f);
rectTransform.anchoredPosition = new Vector2(300f, 0f);
var image = imageObject.GetComponent<Image>();
image.preserveAspect = true;
var imagePlayer = imageObject.AddComponent<FrameAnimationPlayer>();
imagePlayer.ConfigureForAuthoring(directGraph, true, 1f);
EditorSceneManager.MarkSceneDirty(scene);
if (!EditorSceneManager.SaveScene(scene, ScenePath))
{
throw new InvalidOperationException($"无法保存样例场景:{ScenePath}");
}
}
private static T CreateOrLoadAsset<T>(string path) where T : ScriptableObject
{
var asset = AssetDatabase.LoadAssetAtPath<T>(path);
if (asset != null)
{
return asset;
}
asset = ScriptableObject.CreateInstance<T>();
AssetDatabase.CreateAsset(asset, path);
return asset;
}
private static string FindExistingNodeId(FrameAnimationGraph graph, string clipId)
{
var existing = graph.Nodes.FirstOrDefault(node => node != null && node.ClipId == clipId);
return existing != null && FrameAnimationValueUtility.IsValidInternalId(existing.InternalId)
? existing.InternalId
: null;
}
private static int ExtractFrameNumber(string spriteName)
{
if (string.IsNullOrEmpty(spriteName))
{
return int.MaxValue;
}
var spaceIndex = spriteName.LastIndexOf(' ');
var dotIndex = spriteName.LastIndexOf('.');
if (spaceIndex >= 0 && dotIndex > spaceIndex &&
int.TryParse(spriteName.Substring(spaceIndex + 1, dotIndex - spaceIndex - 1), out var number))
{
return number;
}
return int.MaxValue;
}
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;
}
}
}
}