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 frames = new List(); List animations = new List(); 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(metaJson); // 解析frameTags if (meta.frameTags != null) { foreach (var tag in meta.frameTags) { List frameIndices = new List(); 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 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(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(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(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 frames = new List(); 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; } // 使用左上角为(0,0)的坐标系,向右向下为正轴 // 在SliceSprites中会统一转换为Unity的左下角坐标系 int x = column * spriteWidth; int y = row * spriteHeight; frames.Add(new FrameData { name = $"frame_{frameIndex}", x = x, y = y, width = spriteWidth, height = spriteHeight, duration = manualData.frameDuration }); } // 处理动画片段 List animations = new List(); 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 frames, Vector2 pivot) { TextureImporter textureImporter = AssetImporter.GetAtPath(texturePath) as TextureImporter; if (textureImporter == null) { throw new Exception($"无法加载纹理导入器: {texturePath}"); } // 获取纹理尺寸 Texture2D texture = AssetDatabase.LoadAssetAtPath(texturePath); if (texture == null) { throw new Exception($"无法加载纹理: {texturePath}"); } int textureHeight = texture.height; // 设置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数组 // 注意:JSON中的坐标是以左上角为(0,0),向右向下为正轴 // Unity的纹理坐标系统是左下角为(0,0),向上为正轴 // 需要将Y坐标从左上角坐标系转换为Unity的左下角坐标系 var spriteRects = new SpriteRect[frames.Count]; for (int i = 0; i < frames.Count; i++) { var frame = frames[i]; // 坐标转换:从左上角(0,0)坐标系转换为Unity左下角(0,0)坐标系 // Unity Y = 纹理高度 - (原始Y + 高度) float unityY = textureHeight - (frame.y + frame.height); // 根据 pivot 值确定 alignment SpriteAlignment alignment = SpriteAlignment.Custom; if (pivot == new Vector2(0.5f, 0.5f)) alignment = SpriteAlignment.Center; else if (pivot == new Vector2(0f, 1f)) alignment = SpriteAlignment.TopLeft; else if (pivot == new Vector2(1f, 1f)) alignment = SpriteAlignment.TopRight; else if (pivot == new Vector2(0f, 0f)) alignment = SpriteAlignment.BottomLeft; else if (pivot == new Vector2(1f, 0f)) alignment = SpriteAlignment.BottomRight; else if (pivot == new Vector2(0.5f, 1f)) alignment = SpriteAlignment.TopCenter; else if (pivot == new Vector2(0.5f, 0f)) alignment = SpriteAlignment.BottomCenter; else if (pivot == new Vector2(0f, 0.5f)) alignment = SpriteAlignment.LeftCenter; else if (pivot == new Vector2(1f, 0.5f)) alignment = SpriteAlignment.RightCenter; spriteRects[i] = new SpriteRect { name = frame.name, rect = new Rect(frame.x, unityY, frame.width, frame.height), pivot = pivot, alignment = alignment }; } dataProvider.SetSpriteRects(spriteRects); dataProvider.Apply(); AssetDatabase.ImportAsset(texturePath, ImportAssetOptions.ForceUpdate); } // 创建或更新 AnimationClip(若已存在则在原资源上就地更新,避免引用丢失) public static AnimationClip CreateAnimationClip( ProcessedAnimationClip animationData, List frames, Sprite[] sprites, string outputPath, bool isImageAnimation = false) { // 先计算目标资源路径 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); } } // 如果已经存在同名 AnimationClip,则复用该资源,在其上就地更新 AnimationClip clip = AssetDatabase.LoadAssetAtPath(assetPath); bool isNewClip = false; if (clip == null) { clip = new AnimationClip(); isNewClip = true; } clip.name = animationData.name; // 设置采样率 AnimationClipSettings settings = AnimationUtility.GetAnimationClipSettings(clip); clip.frameRate = animationData.sampleRate; // 创建Sprite关键帧 List keyframes = new List(); 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[^1].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); // 强制更新动画长度 (m_StopTime) - 修复复用已有 clip 时长度未更新的问题 float totalDuration = keyframes.Count > 0 ? keyframes[keyframes.Count - 1].time : 0f; SerializedObject serializedClip = new SerializedObject(clip); SerializedProperty stopTimeProp = serializedClip.FindProperty("m_StopTime"); if (stopTimeProp != null) { stopTimeProp.floatValue = totalDuration; serializedClip.ApplyModifiedProperties(); } // 保存 / 更新 AnimationClip 资源 if (isNewClip) { // 不存在则创建新资源 AssetDatabase.CreateAsset(clip, assetPath); } else { // 已存在则标记为已修改,保持原 GUID 与引用 EditorUtility.SetDirty(clip); } AssetDatabase.SaveAssets(); return clip; } // 主流程:生成AnimationClip public static void GenerateClips(string texturePath, string jsonPath, string outputPath, bool isImageAnimation = false, Vector2 pivot = default) { // 如果没有提供 pivot,使用默认值(中心) if (pivot == default) { pivot = new Vector2(0.5f, 0.5f); } // 确保 pivot 值在有效范围内 pivot.x = Mathf.Clamp01(pivot.x); pivot.y = Mathf.Clamp01(pivot.y); 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); } // Debug.Log($"processedData: {processedData.ToString()}"); EditorUtility.DisplayProgressBar("生成AnimationClip", "切图...", 0.4f); // 如果是Single模式,需要切图 if (spriteMode == SpriteImportMode.Single) { SliceSprites(texturePath, processedData.frames, pivot); // 重新加载以确保sprites可用 AssetDatabase.Refresh(); } EditorUtility.DisplayProgressBar("生成AnimationClip", "加载Sprites...", 0.6f); // 加载所有Sprites Sprite[] allSpritesArray = AssetDatabase.LoadAllAssetsAtPath(texturePath) .OfType() .ToArray(); if (allSpritesArray.Length == 0) { throw new Exception("无法加载任何Sprite,请检查切图设置"); } // 创建Sprite字典以便快速查找 Dictionary 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; } } } }