using System; using System.Collections.Generic; using System.Security.Cryptography; using System.Text; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using UnityEditor.Animations; using UnityEngine; namespace UnityEditor.U2D.Aseprite { internal class UniqueNameGenerator { readonly Dictionary> m_NameHashes = new(); public string GetUniqueName(string name, int parentIndex = -1, bool logNewNameGenerated = false, UnityEngine.Object context = null) { if (!m_NameHashes.ContainsKey(parentIndex)) m_NameHashes.Add(parentIndex, new HashSet()); var nameHashes = m_NameHashes[parentIndex]; return GetUniqueName(name, nameHashes, logNewNameGenerated, context); } static string GetUniqueName(string name, HashSet stringHash, bool logNewNameGenerated = false, UnityEngine.Object context = null) { var sanitizedName = string.Copy(SanitizeName(name)); string uniqueName = sanitizedName; int index = 1; while (true) { var hash = GetStringHash(uniqueName); if (!stringHash.Contains(hash)) { stringHash.Add(hash); if (logNewNameGenerated && sanitizedName != uniqueName) Debug.Log($"Asset name {name} is changed to {uniqueName} to ensure uniqueness", context); return uniqueName; } uniqueName = $"{sanitizedName}_{index}"; ++index; } } static string SanitizeName(string name) { name = name.Replace('\0', ' '); string newName = null; // We can't create asset name with these name. if ((name.Length == 2 && name[0] == '.' && name[1] == '.') || (name.Length == 1 && name[0] == '.') || (name.Length == 1 && name[0] == '/')) newName += name + "_"; if (!string.IsNullOrEmpty(newName)) { Debug.LogWarning($"File contains layer with invalid name for generating asset. {name} is renamed to {newName}"); return newName; } return name; } static int GetStringHash(string str) { var md5Hasher = MD5.Create(); var hashed = md5Hasher.ComputeHash(Encoding.UTF8.GetBytes(str)); return BitConverter.ToInt32(hashed, 0); } } internal static class ImportUtilities { public static void SaveAllPalettesToDisk(AsepriteFile file) { for (var i = 0; i < file.frameData.Count; ++i) { var frame = file.frameData[i]; for (var m = 0; m < frame.chunkCount; ++m) { var chunk = frame.chunks[m]; if (chunk.chunkType == ChunkTypes.Palette) PaletteToDisk(chunk as PaletteChunk); } } } static void PaletteToDisk(PaletteChunk palette) { var noOfEntries = palette.noOfEntries; const int cellSize = 32; const int columns = 3; var rows = Mathf.CeilToInt(noOfEntries / (float)3); const int width = columns * cellSize; var height = rows * cellSize; var buffer = new Color32[width * height]; for (var i = 0; i < buffer.Length; ++i) { var x = i % width; var y = i / width; var imgColumn = x / 32; var imgRow = y / 32; var paletteEntry = imgColumn + (imgRow * columns); if (paletteEntry < palette.noOfEntries) buffer[i] = palette.entries[paletteEntry].color; } SaveToPng(buffer, width, height); } public static string SaveToPng(NativeArray buffer, int width, int height) { return SaveToPng(buffer.ToArray(), width, height); } static string SaveToPng(Color32[] buffer, int width, int height) { if (width == 0 || height == 0) return "No .png generated."; var texture2D = new Texture2D(width, height); texture2D.SetPixels32(buffer); var png = texture2D.EncodeToPNG(); var path = Application.dataPath + $"/tex_{System.Guid.NewGuid().ToString()}.png"; var fileStream = System.IO.File.Create(path); fileStream.Write(png); fileStream.Close(); UnityEngine.Object.DestroyImmediate(texture2D); return path; } public static void ExportAnimationAssets(AsepriteImporter[] importers, bool exportClips, bool exportController) { var savePath = EditorUtility.SaveFolderPanel( "Export Animation Assets", Application.dataPath, ""); ExportAnimationAssets(savePath, importers, exportClips, exportController); } public static void ExportAnimationAssets(string savePath, AsepriteImporter[] importers, bool exportClips, bool exportController) { if (string.IsNullOrEmpty(savePath)) return; for (var i = 0; i < importers.Length; ++i) { var importedObjectPath = importers[i].assetPath; AnimationClip[] clips; if (exportClips) clips = ExportAnimationClips(importedObjectPath, savePath); else clips = GetAllClipsFromController(importedObjectPath); if (exportController) ExportAnimatorController(importers[i], clips, savePath); } } static AnimationClip[] ExportAnimationClips(string importedObjectPath, string path) { var relativePath = FileUtil.GetProjectRelativePath(path); var animationClips = GetAllClipsFromController(importedObjectPath); var clips = new List(); for (var i = 0; i < animationClips.Length; ++i) { var clip = animationClips[i]; var clipPath = $"{relativePath}/{clip.name}.anim"; var result = AssetDatabase.ExtractAsset(clip, clipPath); if (!string.IsNullOrEmpty(result)) Debug.LogWarning(result); var newClip = AssetDatabase.LoadAssetAtPath(clipPath); clips.Add(newClip); } return clips.ToArray(); } static AnimationClip[] GetAllClipsFromController(string assetPath) { var controller = AssetDatabase.LoadAssetAtPath(assetPath); return controller.animationClips; } static void ExportAnimatorController(AsepriteImporter importer, AnimationClip[] clips, string path) { var relativePath = FileUtil.GetProjectRelativePath(path); var importedObjectPath = importer.assetPath; var fileName = System.IO.Path.GetFileNameWithoutExtension(importedObjectPath); var controllerPath = $"{relativePath}/{fileName}.controller"; var controller = AnimatorController.CreateAnimatorControllerAtPath(controllerPath); for (var i = 0; i < clips.Length; ++i) controller.AddMotion(clips[i]); } public static Vector2 CalculateCellPivot(RectInt cellRect, Vector2Int canvasSize, SpriteAlignment alignment, Vector2 customPivot) { if (cellRect.width == 0 || cellRect.height == 0) return Vector2.zero; var scaleX = canvasSize.x / (float)cellRect.width; var scaleY = canvasSize.y / (float)cellRect.height; var pivot = new Vector2(cellRect.x / (float)canvasSize.x, cellRect.y / (float)canvasSize.y); pivot *= -1f; Vector2 alignmentPos; if (alignment == SpriteAlignment.Custom) alignmentPos = customPivot; else alignmentPos = PivotAlignmentToVector(alignment); pivot.x += alignmentPos.x; pivot.y += alignmentPos.y; pivot.x *= scaleX; pivot.y *= scaleY; return pivot; } public static Vector2 PivotAlignmentToVector(SpriteAlignment alignment) { switch (alignment) { case SpriteAlignment.Center: return new Vector2(0.5f, 0.5f); case SpriteAlignment.TopLeft: return new Vector2(0f, 1f); case SpriteAlignment.TopCenter: return new Vector2(0.5f, 1f); case SpriteAlignment.TopRight: return new Vector2(1f, 1f); case SpriteAlignment.LeftCenter: return new Vector2(0f, 0.5f); case SpriteAlignment.RightCenter: return new Vector2(1f, 0.5f); case SpriteAlignment.BottomLeft: return new Vector2(0f, 0f); case SpriteAlignment.BottomCenter: return new Vector2(0.5f, 0f); case SpriteAlignment.BottomRight: return new Vector2(1f, 0f); default: return new Vector2(0f, 0f); } } public static string GetCellName(string baseName, int frameIndex, int noOfFrames) { if (noOfFrames == 1) return baseName; return $"{baseName}_Frame_{frameIndex}"; } public static void DisposeIfCreated(this NativeArray arr) where T : struct { if (arr == default || !arr.IsCreated) return; var handle = NativeArrayUnsafeUtility.GetAtomicSafetyHandle(arr); if (!AtomicSafetyHandle.IsHandleValid(handle)) return; arr.Dispose(); } public static bool IsLayerVisible(int layerIndex, in List layers) { var layer = layers[layerIndex]; var isVisible = (layer.layerFlags & LayerFlags.Visible) != 0; if (!isVisible) return false; if (layer.parentIndex != -1) isVisible = IsLayerVisible(layer.parentIndex, in layers); return isVisible; } #if !UNITY_2023_1_OR_NEWER public static bool IsEqual(this RectInt rectA, RectInt rectB) { return rectA.x == rectB.x && rectA.y == rectB.y && rectA.width == rectB.width && rectA.height == rectB.height; } #endif } }