安卓优化
This commit is contained in:
@@ -0,0 +1,603 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user