// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik // #if UNITY_EDITOR using System; using UnityEditor; using UnityEngine; #if UNITY_2D_SPRITE using UnityEditor.U2D.Sprites; #else #pragma warning disable CS0618 // Type or member is obsolete. #endif namespace Animancer.Editor.Tools { /// A wrapper around the '2D Sprite' package features for editing Sprite data. public class SpriteDataEditor { /************************************************************************************************************************/ #if UNITY_2D_SPRITE /************************************************************************************************************************/ private static SpriteDataProviderFactories _Factories; private static SpriteDataProviderFactories Factories { get { if (_Factories == null) { _Factories = new(); _Factories.Init(); } return _Factories; } } /************************************************************************************************************************/ private readonly ISpriteEditorDataProvider Provider; private SpriteRect[] _SpriteRects; /************************************************************************************************************************/ /// The number of sprites in the target data. public int SpriteCount { get => _SpriteRects.Length; set { Array.Resize(ref _SpriteRects, value); for (int i = 0; i < _SpriteRects.Length; i++) _SpriteRects[i] = new(); } } /// Returns the name of the sprite at the specified `index`. public string GetName(int index) => _SpriteRects[index].name; /// Sets the name of the sprite at the specified `index`. public void SetName(int index, string name) => _SpriteRects[index].name = name; /// Returns the rect of the sprite at the specified `index`. public Rect GetRect(int index) => _SpriteRects[index].rect; /// Sets the rect of the sprite at the specified `index`. public void SetRect(int index, Rect rect) => _SpriteRects[index].rect = rect; /// Returns the pivot of the sprite at the specified `index`. public Vector2 GetPivot(int index) => _SpriteRects[index].pivot; /// Sets the pivot of the sprite at the specified `index`. public void SetPivot(int index, Vector2 pivot) { _SpriteRects[index].pivot = pivot; _SpriteRects[index].alignment = GetSpriteAlignment(pivot); } /// Returns the alignment of the sprite at the specified `index`. public SpriteAlignment GetAlignment(int index) => _SpriteRects[index].alignment; /// Sets the alignment of the sprite at the specified `index`. public void SetAlignment(int index, SpriteAlignment alignment) => _SpriteRects[index].alignment = alignment; /// Returns the border of the sprite at the specified `index`. public Vector4 GetBorder(int index) => _SpriteRects[index].border; /// Sets the border of the sprite at the specified `index`. public void SetBorder(int index, Vector4 border) => _SpriteRects[index].border = border; /************************************************************************************************************************/ #else /************************************************************************************************************************/ private SpriteMetaData[] _SpriteSheet; /************************************************************************************************************************/ /// The number of sprites in the target data. public int SpriteCount { get => _SpriteSheet.Length; set => Array.Resize(ref _SpriteSheet, value); } /// Returns the name of the sprite at the specified `index`. public string GetName(int index) => _SpriteSheet[index].name; /// Sets the name of the sprite at the specified `index`. public void SetName(int index, string name) => _SpriteSheet[index].name = name; /// Returns the rect of the sprite at the specified `index`. public Rect GetRect(int index) => _SpriteSheet[index].rect; /// Sets the rect of the sprite at the specified `index`. public void SetRect(int index, Rect rect) => _SpriteSheet[index].rect = rect; /// Returns the pivot of the sprite at the specified `index`. public Vector2 GetPivot(int index) => _SpriteSheet[index].pivot; /// Sets the pivot of the sprite at the specified `index`. public void SetPivot(int index, Vector2 pivot) { _SpriteSheet[index].pivot = pivot; _SpriteSheet[index].alignment = (int)GetSpriteAlignment(pivot); } /// Returns the alignment of the sprite at the specified `index`. public SpriteAlignment GetAlignment(int index) => (SpriteAlignment)_SpriteSheet[index].alignment; /// Sets the alignment of the sprite at the specified `index`. public void SetAlignment(int index, SpriteAlignment alignment) => _SpriteSheet[index].alignment = (int)alignment; /// Returns the border of the sprite at the specified `index`. public Vector4 GetBorder(int index) => _SpriteSheet[index].border; /// Sets the border of the sprite at the specified `index`. public void SetBorder(int index, Vector4 border) => _SpriteSheet[index].border = border; /************************************************************************************************************************/ #endif /************************************************************************************************************************/ /// Returns the appropriate alignment for the given `pivot`. public static SpriteAlignment GetSpriteAlignment(Vector2 pivot) { switch (pivot.x) { case 0: switch (pivot.y) { case 0: return SpriteAlignment.BottomLeft; case 0.5f: return SpriteAlignment.LeftCenter; case 1: return SpriteAlignment.TopLeft; } break; case 0.5f: switch (pivot.y) { case 0: return SpriteAlignment.BottomCenter; case 0.5f: return SpriteAlignment.Center; case 1: return SpriteAlignment.TopCenter; } break; case 1: switch (pivot.y) { case 0: return SpriteAlignment.BottomRight; case 0.5f: return SpriteAlignment.RightCenter; case 1: return SpriteAlignment.TopRight; } break; } return SpriteAlignment.Custom; } /************************************************************************************************************************/ private readonly TextureImporter Importer; /************************************************************************************************************************/ /// Creates a new . public SpriteDataEditor(TextureImporter importer) { Importer = importer; #if UNITY_2D_SPRITE Provider = Factories.GetSpriteEditorDataProviderFromObject(importer); Provider.InitSpriteEditorDataProvider(); _SpriteRects = Provider.GetSpriteRects(); #else _SpriteSheet = importer.spritesheet; #endif } /************************************************************************************************************************/ /// Tries to find the index of the data matching the `sprite`. /// /// Returns -1 if there is no data matching the . /// /// Returns -2 if there is more than one data matching the but no /// match. /// public int IndexOf(Sprite sprite) { var nameMatchIndex = -1; var count = SpriteCount; for (int i = 0; i < count; i++) { if (GetName(i) == sprite.name) { if (GetRect(i) == sprite.rect) return i; if (nameMatchIndex == -1)// First name match. nameMatchIndex = i; else nameMatchIndex = -2;// Already found 2 name matches. } } if (nameMatchIndex == -1) { Debug.LogError($"No {nameof(SpriteMetaData)} for '{sprite.name}' was found.", sprite); } else if (nameMatchIndex == -2) { Debug.LogError($"More than one {nameof(SpriteMetaData)} for '{sprite.name}' was found" + $" but none of them matched the {nameof(Sprite)}.{nameof(Sprite.rect)}." + $" If the texture's Max Size is smaller than its actual size, increase the Max Size before performing this" + $" operation so that the {nameof(Rect)}s can be used to identify the correct data.", sprite); } return nameMatchIndex; } /************************************************************************************************************************/ /// Logs an error and returns false if the data at the specified `index` is out of the texture bounds. public bool ValidateBounds(int index, Sprite sprite) { var rect = GetRect(index); if (rect.xMin >= 0 && rect.yMin >= 0 && rect.xMax <= sprite.texture.width && rect.yMax <= sprite.texture.height) return true; var path = AssetDatabase.GetAssetPath(sprite); // The Max Texture Size import setting may cause the loaded texture to be smaller than the actual image. // Sprite dimensions are defined against the actual image though, so we need to check those bounds. var importer = (TextureImporter)AssetImporter.GetAtPath(path); importer.GetSourceTextureWidthAndHeight(out var width, out var height); if (rect.xMin >= 0 && rect.yMin >= 0 && rect.xMax <= width && rect.yMax <= height) return true; Debug.LogError( $"This modification would put '{sprite.name}' at {rect}" + $" which is outside of the texture ({width}x{height})" + $" so '{path}' was not modified.", sprite); return false; } /************************************************************************************************************************/ /// Applies any modifications to the target asset. public void Apply() { #if UNITY_2D_SPRITE Provider.SetSpriteRects(_SpriteRects); Provider.Apply(); #else Importer.spritesheet = _SpriteSheet; EditorUtility.SetDirty(Importer); #endif Importer.SaveAndReimport(); } /************************************************************************************************************************/ } } #endif