Files
aibis-dream/Assets/Editor/Window/AnimationClipGeneratorCore.cs
T
2025-12-22 21:10:37 +08:00

604 lines
23 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.U2D.Sprites;
using UnityEngine;
namespace AibisDream.SystemEditor
{
public static class AnimationClipGeneratorCore
{
// 检查SpriteMode
public static SpriteImportMode CheckSpriteMode(string texturePath)
{
TextureImporter textureImporter = AssetImporter.GetAtPath(texturePath) as TextureImporter;
if (textureImporter == null)
{
throw new Exception($"无法加载纹理导入器: {texturePath}");
}
return textureImporter.spriteImportMode;
}
// 解析Aseprite JSON
public static ProcessedAnimationData ParseAsepriteJson(string jsonPath, string texturePath)
{
string jsonContent = File.ReadAllText(jsonPath);
// Unity的JsonUtility不支持Dictionary,需要手动解析frames对象
// 使用SimpleJSON或手动解析
ProcessedAnimationData result = ParseAsepriteJsonManual(jsonContent);
if (result.frames == null || result.frames.Count == 0)
{
throw new Exception("无效的Aseprite JSON格式:无法解析frames");
}
return result;
}
private static ProcessedAnimationData ParseAsepriteJsonManual(string jsonContent)
{
List<FrameData> frames = new List<FrameData>();
List<ProcessedAnimationClip> animations = new List<ProcessedAnimationClip>();
try
{
// 解析meta部分
int metaStart = jsonContent.IndexOf("\"meta\":");
if (metaStart == -1)
{
throw new Exception("找不到meta部分");
}
// 找到meta对象的结束位置
int metaBraceStart = jsonContent.IndexOf('{', metaStart);
int metaBraceEnd = FindMatchingBrace(jsonContent, metaBraceStart);
string metaJson = jsonContent.Substring(metaBraceStart, metaBraceEnd - metaBraceStart + 1);
// 移除可能的尾随逗号
metaJson = metaJson.TrimEnd(',', ' ', '\n', '\r');
AsepriteMeta meta = JsonUtility.FromJson<AsepriteMeta>(metaJson);
// 解析frameTags
if (meta.frameTags != null)
{
foreach (var tag in meta.frameTags)
{
List<int> frameIndices = new List<int>();
int start = tag.from;
int end = tag.to;
if (tag.direction == "forward" || tag.direction == "pingpong" || string.IsNullOrEmpty(tag.direction))
{
for (int i = start; i <= end; i++)
{
frameIndices.Add(i);
}
}
else if (tag.direction == "reverse")
{
for (int i = end; i >= start; i--)
{
frameIndices.Add(i);
}
}
animations.Add(new ProcessedAnimationClip
{
name = tag.name,
frameIndices = frameIndices,
loop = true,
frameDuration = 0,
sampleRate = 100
});
}
}
// 解析frames对象 - 使用正则表达式或手动解析
int framesStart = jsonContent.IndexOf("\"frames\":");
if (framesStart == -1)
{
throw new Exception("找不到frames部分");
}
// 找到frames对象的开始和结束
int framesBraceStart = jsonContent.IndexOf('{', framesStart);
int framesBraceEnd = FindMatchingBrace(jsonContent, framesBraceStart);
string framesContent = jsonContent.Substring(framesBraceStart + 1, framesBraceEnd - framesBraceStart - 1);
// 解析每个帧条目
ParseFramesContent(framesContent, frames);
// 按名称排序帧(确保顺序正确)
frames = frames.OrderBy(f => ExtractFrameNumber(f.name)).ThenBy(f => f.name).ToList();
}
catch (Exception e)
{
throw new Exception($"解析Aseprite JSON失败: {e.Message}\n{e.StackTrace}");
}
return new ProcessedAnimationData
{
frames = frames,
animations = animations
};
}
private static void ParseFramesContent(string framesContent, List<FrameData> frames)
{
int pos = 0;
while (pos < framesContent.Length)
{
// 跳过空白字符和逗号
while (pos < framesContent.Length && (char.IsWhiteSpace(framesContent[pos]) || framesContent[pos] == ','))
pos++;
if (pos >= framesContent.Length) break;
// 查找帧名称(在引号中)
if (framesContent[pos] != '"')
{
pos++;
continue;
}
int nameStart = pos + 1;
int nameEnd = framesContent.IndexOf('"', nameStart);
if (nameEnd == -1) break;
string frameName = framesContent.Substring(nameStart, nameEnd - nameStart);
pos = nameEnd + 1;
// 跳过冒号和空白
while (pos < framesContent.Length && (char.IsWhiteSpace(framesContent[pos]) || framesContent[pos] == ':'))
pos++;
if (pos >= framesContent.Length) break;
// 查找帧数据对象
if (framesContent[pos] != '{')
{
continue;
}
int dataStart = pos;
int dataEnd = FindMatchingBrace(framesContent, dataStart);
if (dataEnd == -1) break;
string frameDataJson = framesContent.Substring(dataStart, dataEnd - dataStart + 1);
pos = dataEnd + 1;
try
{
AsepriteFrameInfo frameInfo = JsonUtility.FromJson<AsepriteFrameInfo>(frameDataJson);
if (frameInfo.frame != null)
{
frames.Add(new FrameData
{
name = frameName,
x = frameInfo.frame.x,
y = frameInfo.frame.y,
width = frameInfo.frame.w,
height = frameInfo.frame.h,
duration = frameInfo.duration
});
}
}
catch (Exception e)
{
Debug.LogWarning($"解析帧数据失败: {frameName}, 错误: {e.Message}");
}
}
}
private static int ExtractFrameNumber(string frameName)
{
// 尝试从帧名称中提取数字(例如 "调酒妹侧头 0.aseprite" -> 0
int lastSpace = frameName.LastIndexOf(' ');
if (lastSpace >= 0 && lastSpace < frameName.Length - 1)
{
string numberPart = frameName.Substring(lastSpace + 1);
int dotIndex = numberPart.IndexOf('.');
if (dotIndex > 0)
{
numberPart = numberPart.Substring(0, dotIndex);
}
if (int.TryParse(numberPart, out int number))
{
return number;
}
}
return int.MaxValue; // 如果无法解析,放在最后
}
private static int FindMatchingBrace(string str, int startIndex)
{
if (startIndex < 0 || startIndex >= str.Length) return -1;
int braceCount = 0;
bool foundStart = false;
for (int i = startIndex; i < str.Length; i++)
{
if (str[i] == '{')
{
braceCount++;
foundStart = true;
}
else if (str[i] == '}')
{
braceCount--;
if (braceCount == 0 && foundStart)
{
return i;
}
}
}
return -1;
}
// 解析手动JSON
public static ProcessedAnimationData ParseManualJson(string jsonPath, string texturePath)
{
string jsonContent = File.ReadAllText(jsonPath);
ManualJsonData manualData = JsonUtility.FromJson<ManualJsonData>(jsonContent);
if (manualData.layout == null || manualData.animations == null)
{
throw new Exception("无效的手动JSON格式");
}
// 验证layout参数
if (manualData.layout.rows <= 0 || manualData.layout.columns <= 0)
{
throw new Exception("rows和columns必须大于0");
}
// 读取图片尺寸
TextureImporter textureImporter = AssetImporter.GetAtPath(texturePath) as TextureImporter;
if (textureImporter == null)
{
throw new Exception($"无法加载纹理导入器: {texturePath}");
}
Texture2D texture = AssetDatabase.LoadAssetAtPath<Texture2D>(texturePath);
if (texture == null)
{
throw new Exception($"无法加载纹理: {texturePath}");
}
int imageWidth = texture.width;
int imageHeight = texture.height;
// 计算sprite尺寸
int spriteWidth = imageWidth / manualData.layout.columns;
int spriteHeight = imageHeight / manualData.layout.rows;
// 验证图片尺寸能被行列数整除
if (imageWidth % manualData.layout.columns != 0 || imageHeight % manualData.layout.rows != 0)
{
throw new Exception($"图片尺寸({imageWidth}x{imageHeight})无法被行列数({manualData.layout.columns}x{manualData.layout.rows})整除");
}
// 计算总帧数
int totalFrames = manualData.layout.frameCount ?? (manualData.layout.rows * manualData.layout.columns);
if (totalFrames > manualData.layout.rows * manualData.layout.columns)
{
throw new Exception($"frameCount({totalFrames})不能超过rows*columns({manualData.layout.rows * manualData.layout.columns})");
}
// 生成帧数据
List<FrameData> frames = new List<FrameData>();
bool isHorizontal = manualData.layout.direction == "horizontal";
for (int frameIndex = 0; frameIndex < totalFrames; frameIndex++)
{
int row, column;
if (isHorizontal)
{
// 横向排列:按行扫描,从左到右,从上到下
row = frameIndex / manualData.layout.columns;
column = frameIndex % manualData.layout.columns;
}
else
{
// 纵向排列:按列扫描,从上到下,从左到右
column = frameIndex / manualData.layout.rows;
row = frameIndex % manualData.layout.rows;
}
// Unity Y轴从下往上,需要转换
int x = column * spriteWidth;
int y = (manualData.layout.rows - 1 - row) * spriteHeight;
frames.Add(new FrameData
{
name = $"frame_{frameIndex}",
x = x,
y = y,
width = spriteWidth,
height = spriteHeight,
duration = manualData.frameDuration
});
}
// 处理动画片段
List<ProcessedAnimationClip> animations = new List<ProcessedAnimationClip>();
foreach (var anim in manualData.animations)
{
// 应用参数覆盖逻辑
float frameDuration = anim.frameDuration ?? manualData.frameDuration;
int sampleRate = anim.sampleRate ?? manualData.sampleRate;
animations.Add(new ProcessedAnimationClip
{
name = anim.name,
frameIndices = anim.frameIndices,
loop = anim.loop,
frameDuration = frameDuration,
sampleRate = sampleRate
});
}
return new ProcessedAnimationData
{
frames = frames,
animations = animations
};
}
// 根据JSON信息切图
public static void SliceSprites(string texturePath, List<FrameData> frames)
{
TextureImporter textureImporter = AssetImporter.GetAtPath(texturePath) as TextureImporter;
if (textureImporter == null)
{
throw new Exception($"无法加载纹理导入器: {texturePath}");
}
// 设置spriteMode为Multiple
textureImporter.spriteImportMode = SpriteImportMode.Multiple;
// 使用新的Sprite Editor Data Provider API (Unity 2020.2+)
var spriteDataProviderFactories = new SpriteDataProviderFactories();
spriteDataProviderFactories.Init();
var dataProvider = spriteDataProviderFactories.GetSpriteEditorDataProviderFromObject(textureImporter);
if (dataProvider == null)
{
throw new Exception($"无法获取Sprite Editor Data Provider: {texturePath}");
}
dataProvider.InitSpriteEditorDataProvider();
// 创建SpriteRect数组
var spriteRects = new SpriteRect[frames.Count];
for (int i = 0; i < frames.Count; i++)
{
var frame = frames[i];
spriteRects[i] = new SpriteRect
{
name = frame.name,
rect = new Rect(frame.x, frame.y, frame.width, frame.height),
pivot = new Vector2(0.5f, 0.5f),
alignment = SpriteAlignment.Center
};
}
dataProvider.SetSpriteRects(spriteRects);
dataProvider.Apply();
AssetDatabase.ImportAsset(texturePath, ImportAssetOptions.ForceUpdate);
}
// 创建AnimationClip
public static AnimationClip CreateAnimationClip(
ProcessedAnimationClip animationData,
List<FrameData> frames,
Sprite[] sprites,
string outputPath,
bool isImageAnimation = false)
{
AnimationClip clip = new AnimationClip();
clip.name = animationData.name;
// 设置采样率
AnimationClipSettings settings = AnimationUtility.GetAnimationClipSettings(clip);
clip.frameRate = animationData.sampleRate;
// 创建Sprite关键帧
List<ObjectReferenceKeyframe> keyframes = new List<ObjectReferenceKeyframe>();
float currentTime = 0f;
foreach (int frameIndex in animationData.frameIndices)
{
if (frameIndex < 0 || frameIndex >= sprites.Length)
{
Debug.LogWarning($"帧索引{frameIndex}超出范围,跳过");
continue;
}
Sprite sprite = sprites[frameIndex];
if (sprite == null)
{
Debug.LogWarning($"帧索引{frameIndex}的Sprite为空,跳过");
continue;
}
// 使用帧的duration(如果是Aseprite格式)或动画的frameDuration(手动格式)
float frameDuration = frames[frameIndex].duration > 0
? frames[frameIndex].duration / 1000f // 毫秒转秒
: animationData.frameDuration / 1000f;
keyframes.Add(new ObjectReferenceKeyframe
{
time = currentTime,
value = sprite
});
currentTime += frameDuration;
}
if (keyframes.Count == 0)
{
throw new Exception($"动画片段{animationData.name}没有有效的关键帧");
}
// 设置循环
if (animationData.loop && keyframes.Count > 1)
{
// 添加最后一帧的重复,确保循环
keyframes.Add(new ObjectReferenceKeyframe
{
time = currentTime,
value = keyframes[0].value
});
}
// 创建EditorCurveBinding - 根据动画类型选择不同的组件
EditorCurveBinding curveBinding = new EditorCurveBinding
{
path = "",
type = isImageAnimation ? typeof(UnityEngine.UI.Image) : typeof(SpriteRenderer),
propertyName = "m_Sprite"
};
// 设置曲线
AnimationUtility.SetObjectReferenceCurve(clip, curveBinding, keyframes.ToArray());
// 设置循环
settings.loopTime = animationData.loop;
AnimationUtility.SetAnimationClipSettings(clip, settings);
// 保存AnimationClip
string fullPath = Path.Combine(outputPath, $"{animationData.name}.anim");
string assetPath = fullPath.Replace('\\', '/');
if (!assetPath.StartsWith("Assets/"))
{
// 转换为相对路径
string projectPath = Application.dataPath.Replace("Assets", "");
if (assetPath.StartsWith(projectPath))
{
assetPath = assetPath.Substring(projectPath.Length);
}
}
AssetDatabase.CreateAsset(clip, assetPath);
AssetDatabase.SaveAssets();
return clip;
}
// 主流程:生成AnimationClip
public static void GenerateClips(string texturePath, string jsonPath, string outputPath, bool isImageAnimation = false)
{
try
{
EditorUtility.DisplayProgressBar("生成AnimationClip", "检查SpriteMode...", 0f);
// 检查SpriteMode
SpriteImportMode spriteMode = CheckSpriteMode(texturePath);
if (spriteMode == SpriteImportMode.Multiple)
{
Debug.Log("图片的SpriteMode已经是Multiple,将使用现有的Sprite切图");
}
EditorUtility.DisplayProgressBar("生成AnimationClip", "解析JSON...", 0.2f);
// 判断JSON类型并解析
ProcessedAnimationData processedData;
string jsonContent = File.ReadAllText(jsonPath);
if (jsonContent.Contains("\"type\":\"manual\"") || jsonContent.Contains("\"type\": \"manual\""))
{
// 手动格式
processedData = ParseManualJson(jsonPath, texturePath);
}
else
{
// Aseprite格式
processedData = ParseAsepriteJson(jsonPath, texturePath);
}
EditorUtility.DisplayProgressBar("生成AnimationClip", "切图...", 0.4f);
// 如果是Single模式,需要切图
if (spriteMode == SpriteImportMode.Single)
{
SliceSprites(texturePath, processedData.frames);
// 重新加载以确保sprites可用
AssetDatabase.Refresh();
}
EditorUtility.DisplayProgressBar("生成AnimationClip", "加载Sprites...", 0.6f);
// 加载所有Sprites
Sprite[] allSpritesArray = AssetDatabase.LoadAllAssetsAtPath(texturePath)
.OfType<Sprite>()
.ToArray();
if (allSpritesArray.Length == 0)
{
throw new Exception("无法加载任何Sprite,请检查切图设置");
}
// 创建Sprite字典以便快速查找
Dictionary<string, Sprite> spriteDict = allSpritesArray.ToDictionary(s => s.name, s => s);
// 按照frames列表的顺序创建Sprite数组
Sprite[] allSprites = new Sprite[processedData.frames.Count];
for (int i = 0; i < processedData.frames.Count; i++)
{
string frameName = processedData.frames[i].name;
if (spriteDict.TryGetValue(frameName, out Sprite sprite))
{
allSprites[i] = sprite;
}
else
{
Debug.LogWarning($"找不到名为{frameName}的Sprite");
// 尝试使用索引查找
if (i < allSpritesArray.Length)
{
allSprites[i] = allSpritesArray[i];
}
}
}
// 确保输出目录存在
if (!Directory.Exists(outputPath))
{
Directory.CreateDirectory(outputPath);
}
EditorUtility.DisplayProgressBar("生成AnimationClip", "创建AnimationClip...", 0.8f);
// 为每个动画片段创建AnimationClip
int animationCount = processedData.animations.Count;
for (int i = 0; i < animationCount; i++)
{
var anim = processedData.animations[i];
EditorUtility.DisplayProgressBar("生成AnimationClip",
$"创建动画片段: {anim.name}", 0.8f + (i / (float)animationCount) * 0.2f);
CreateAnimationClip(anim, processedData.frames, allSprites, outputPath, isImageAnimation);
}
EditorUtility.ClearProgressBar();
Debug.Log($"成功生成{animationCount}个AnimationClip到: {outputPath}");
}
catch (Exception e)
{
EditorUtility.ClearProgressBar();
Debug.LogError($"生成AnimationClip失败: {e.Message}");
throw;
}
}
}
}