using System; using System.Collections.Generic; using Newtonsoft.Json.Linq; using UnityEngine; namespace AibisDream.FrameAnimation.Editor { internal static class AsepriteJsonParser { public static bool TryParse( string json, string sourceId, out AsepriteSourceDocument document, out FrameAnimationImportIssue issue) { document = null; issue = null; try { if (string.IsNullOrWhiteSpace(json)) { issue = Error(FrameAnimationImportIssueCode.JsonInvalid, sourceId, "Aseprite JSON 为空。"); return false; } var root = JObject.Parse(json); var framesToken = root["frames"]; if (framesToken == null || (framesToken.Type != JTokenType.Object && framesToken.Type != JTokenType.Array)) { issue = Error(FrameAnimationImportIssueCode.FramesMissing, sourceId, "JSON 缺少 Object 或 Array 格式的 frames。"); return false; } var frames = ParseFrames(framesToken); var meta = root["meta"] as JObject; var size = ParseSize(meta?["size"]); var imageName = meta?.Value("image") ?? string.Empty; var tags = ParseTags(meta?["frameTags"]); document = new AsepriteSourceDocument(frames, tags, size, imageName); return true; } catch (Exception exception) { issue = Error( FrameAnimationImportIssueCode.JsonInvalid, sourceId, $"Aseprite JSON 解析失败:{exception.Message}"); return false; } } public static bool TryExpandTag( AsepriteSourceTag tag, int frameCount, string sourceId, out IReadOnlyList indices, out FrameAnimationImportIssue issue) { indices = Array.Empty(); issue = null; if (tag == null || tag.From < 0 || tag.To < tag.From || tag.To >= frameCount) { issue = Error( FrameAnimationImportIssueCode.TagRangeInvalid, sourceId, $"Tag '{tag?.Name}' 的范围 {tag?.From}..{tag?.To} 无效。"); return false; } var result = new List(); switch ((tag.Direction ?? string.Empty).ToLowerInvariant()) { case "forward": AddAscending(result, tag.From, tag.To); break; case "reverse": AddDescending(result, tag.To, tag.From); break; case "pingpong": AddAscending(result, tag.From, tag.To); AddDescending(result, tag.To - 1, tag.From + 1); break; case "pingpong_reverse": AddDescending(result, tag.To, tag.From); AddAscending(result, tag.From + 1, tag.To - 1); break; default: issue = Error( FrameAnimationImportIssueCode.TagDirectionInvalid, sourceId, $"Tag '{tag.Name}' 使用了不支持的 direction '{tag.Direction}'。"); return false; } indices = result; return true; } private static List ParseFrames(JToken token) { var frames = new List(); if (token is JObject objectFrames) { foreach (var property in objectFrames.Properties()) { frames.Add(ParseFrame(property.Name, property.Value)); } } else { foreach (var item in (JArray)token) { frames.Add(ParseFrame(item.Value("filename") ?? string.Empty, item)); } } return frames; } private static AsepriteSourceFrame ParseFrame(string frameName, JToken token) { return new AsepriteSourceFrame( frameName, ParseRect(token["frame"]), token.Value("duration") ?? 0, token.Value("rotated") ?? false, token.Value("trimmed") ?? false, ParseRect(token["spriteSourceSize"]), ParseSize(token["sourceSize"])); } private static List ParseTags(JToken token) { var tags = new List(); if (!(token is JArray array)) { return tags; } foreach (var item in array) { tags.Add(new AsepriteSourceTag( (item.Value("name") ?? string.Empty).Trim(), item.Value("from") ?? -1, item.Value("to") ?? -1, item.Value("direction") ?? string.Empty)); } return tags; } private static RectInt ParseRect(JToken token) { return token == null ? default : new RectInt( token.Value("x") ?? 0, token.Value("y") ?? 0, token.Value("w") ?? 0, token.Value("h") ?? 0); } private static Vector2Int ParseSize(JToken token) { return token == null ? default : new Vector2Int(token.Value("w") ?? 0, token.Value("h") ?? 0); } private static void AddAscending(List target, int from, int to) { for (var index = from; index <= to; index++) { target.Add(index); } } private static void AddDescending(List target, int from, int to) { for (var index = from; index >= to; index--) { target.Add(index); } } private static FrameAnimationImportIssue Error( FrameAnimationImportIssueCode code, string sourceId, string message) { return new FrameAnimationImportIssue( FrameAnimationValidationSeverity.Error, code, sourceId, sourceId, message); } } }