324 lines
12 KiB
C#
324 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using Unity.Burst;
|
|
using Unity.Collections;
|
|
using Unity.Collections.LowLevel.Unsafe;
|
|
using UnityEditor.Animations;
|
|
using UnityEngine;
|
|
|
|
namespace UnityEditor.U2D.Aseprite
|
|
{
|
|
internal class UniqueNameGenerator
|
|
{
|
|
readonly Dictionary<int, HashSet<int>> 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<int>());
|
|
var nameHashes = m_NameHashes[parentIndex];
|
|
return GetUniqueName(name, nameHashes, logNewNameGenerated, context);
|
|
}
|
|
|
|
static string GetUniqueName(string name, HashSet<int> 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<Color32> 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<AnimationClip>();
|
|
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<AnimationClip>(clipPath);
|
|
clips.Add(newClip);
|
|
}
|
|
return clips.ToArray();
|
|
}
|
|
|
|
static AnimationClip[] GetAllClipsFromController(string assetPath)
|
|
{
|
|
var controller = AssetDatabase.LoadAssetAtPath<AnimatorController>(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, uint spritePadding, 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 halfSpritePadding = spritePadding / 2f;
|
|
|
|
var pivot = new Vector2((cellRect.x - halfSpritePadding) / (float)canvasSize.x, (cellRect.y - halfSpritePadding) / (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, bool isMerged)
|
|
{
|
|
if (noOfFrames == 1)
|
|
return baseName;
|
|
return isMerged ? $"Frame_{frameIndex}" : $"{baseName}_Frame_{frameIndex}";
|
|
}
|
|
|
|
public static void DisposeIfCreated<T>(this NativeArray<T> 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, IReadOnlyList<Layer> layers)
|
|
{
|
|
var layer = layers[layerIndex];
|
|
var isVisible = (layer.layerFlags & LayerFlags.Visible) != 0;
|
|
if (!isVisible)
|
|
return false;
|
|
|
|
if (layer.parentIndex != -1)
|
|
isVisible = IsLayerVisible(layer.parentIndex, layers);
|
|
return isVisible;
|
|
}
|
|
|
|
// Burst in 2021.x does not support passing native arrays to bursted methods.
|
|
#if UNITY_2022_2_OR_NEWER
|
|
[BurstCompile]
|
|
#endif
|
|
public static bool IsEmptyImage(in NativeArray<Color32> image)
|
|
{
|
|
for (var i = 0; i < image.Length; ++i)
|
|
{
|
|
if (image[i].a > 0)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#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
|
|
|
|
public static int FindIndex<T>(this IReadOnlyList<T> list, Func<T, bool> predicate)
|
|
{
|
|
for (var i = 0; i < list.Count; ++i)
|
|
{
|
|
if (predicate(list[i]))
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
}
|