SpriteDataEditor.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. #if UNITY_EDITOR
  3. using System;
  4. using UnityEditor;
  5. using UnityEngine;
  6. #if UNITY_2D_SPRITE
  7. using UnityEditor.U2D.Sprites;
  8. #else
  9. #pragma warning disable CS0618 // Type or member is obsolete.
  10. #endif
  11. namespace Animancer.Editor.Tools
  12. {
  13. /// <summary>A wrapper around the '2D Sprite' package features for editing Sprite data.</summary>
  14. public class SpriteDataEditor
  15. {
  16. /************************************************************************************************************************/
  17. #if UNITY_2D_SPRITE
  18. /************************************************************************************************************************/
  19. private static SpriteDataProviderFactories _Factories;
  20. private static SpriteDataProviderFactories Factories
  21. {
  22. get
  23. {
  24. if (_Factories == null)
  25. {
  26. _Factories = new();
  27. _Factories.Init();
  28. }
  29. return _Factories;
  30. }
  31. }
  32. /************************************************************************************************************************/
  33. private readonly ISpriteEditorDataProvider Provider;
  34. private SpriteRect[] _SpriteRects;
  35. /************************************************************************************************************************/
  36. /// <summary>The number of sprites in the target data.</summary>
  37. public int SpriteCount
  38. {
  39. get => _SpriteRects.Length;
  40. set
  41. {
  42. Array.Resize(ref _SpriteRects, value);
  43. for (int i = 0; i < _SpriteRects.Length; i++)
  44. _SpriteRects[i] = new();
  45. }
  46. }
  47. /// <summary>Returns the name of the sprite at the specified `index`.</summary>
  48. public string GetName(int index) => _SpriteRects[index].name;
  49. /// <summary>Sets the name of the sprite at the specified `index`.</summary>
  50. public void SetName(int index, string name) => _SpriteRects[index].name = name;
  51. /// <summary>Returns the rect of the sprite at the specified `index`.</summary>
  52. public Rect GetRect(int index) => _SpriteRects[index].rect;
  53. /// <summary>Sets the rect of the sprite at the specified `index`.</summary>
  54. public void SetRect(int index, Rect rect) => _SpriteRects[index].rect = rect;
  55. /// <summary>Returns the pivot of the sprite at the specified `index`.</summary>
  56. public Vector2 GetPivot(int index) => _SpriteRects[index].pivot;
  57. /// <summary>Sets the pivot of the sprite at the specified `index`.</summary>
  58. public void SetPivot(int index, Vector2 pivot)
  59. {
  60. _SpriteRects[index].pivot = pivot;
  61. _SpriteRects[index].alignment = GetSpriteAlignment(pivot);
  62. }
  63. /// <summary>Returns the alignment of the sprite at the specified `index`.</summary>
  64. public SpriteAlignment GetAlignment(int index) => _SpriteRects[index].alignment;
  65. /// <summary>Sets the alignment of the sprite at the specified `index`.</summary>
  66. public void SetAlignment(int index, SpriteAlignment alignment) => _SpriteRects[index].alignment = alignment;
  67. /// <summary>Returns the border of the sprite at the specified `index`.</summary>
  68. public Vector4 GetBorder(int index) => _SpriteRects[index].border;
  69. /// <summary>Sets the border of the sprite at the specified `index`.</summary>
  70. public void SetBorder(int index, Vector4 border) => _SpriteRects[index].border = border;
  71. /************************************************************************************************************************/
  72. #else
  73. /************************************************************************************************************************/
  74. private SpriteMetaData[] _SpriteSheet;
  75. /************************************************************************************************************************/
  76. /// <summary>The number of sprites in the target data.</summary>
  77. public int SpriteCount
  78. {
  79. get => _SpriteSheet.Length;
  80. set => Array.Resize(ref _SpriteSheet, value);
  81. }
  82. /// <summary>Returns the name of the sprite at the specified `index`.</summary>
  83. public string GetName(int index) => _SpriteSheet[index].name;
  84. /// <summary>Sets the name of the sprite at the specified `index`.</summary>
  85. public void SetName(int index, string name) => _SpriteSheet[index].name = name;
  86. /// <summary>Returns the rect of the sprite at the specified `index`.</summary>
  87. public Rect GetRect(int index) => _SpriteSheet[index].rect;
  88. /// <summary>Sets the rect of the sprite at the specified `index`.</summary>
  89. public void SetRect(int index, Rect rect) => _SpriteSheet[index].rect = rect;
  90. /// <summary>Returns the pivot of the sprite at the specified `index`.</summary>
  91. public Vector2 GetPivot(int index) => _SpriteSheet[index].pivot;
  92. /// <summary>Sets the pivot of the sprite at the specified `index`.</summary>
  93. public void SetPivot(int index, Vector2 pivot)
  94. {
  95. _SpriteSheet[index].pivot = pivot;
  96. _SpriteSheet[index].alignment = (int)GetSpriteAlignment(pivot);
  97. }
  98. /// <summary>Returns the alignment of the sprite at the specified `index`.</summary>
  99. public SpriteAlignment GetAlignment(int index) => (SpriteAlignment)_SpriteSheet[index].alignment;
  100. /// <summary>Sets the alignment of the sprite at the specified `index`.</summary>
  101. public void SetAlignment(int index, SpriteAlignment alignment) => _SpriteSheet[index].alignment = (int)alignment;
  102. /// <summary>Returns the border of the sprite at the specified `index`.</summary>
  103. public Vector4 GetBorder(int index) => _SpriteSheet[index].border;
  104. /// <summary>Sets the border of the sprite at the specified `index`.</summary>
  105. public void SetBorder(int index, Vector4 border) => _SpriteSheet[index].border = border;
  106. /************************************************************************************************************************/
  107. #endif
  108. /************************************************************************************************************************/
  109. /// <summary>Returns the appropriate alignment for the given `pivot`.</summary>
  110. public static SpriteAlignment GetSpriteAlignment(Vector2 pivot)
  111. {
  112. switch (pivot.x)
  113. {
  114. case 0:
  115. switch (pivot.y)
  116. {
  117. case 0: return SpriteAlignment.BottomLeft;
  118. case 0.5f: return SpriteAlignment.LeftCenter;
  119. case 1: return SpriteAlignment.TopLeft;
  120. }
  121. break;
  122. case 0.5f:
  123. switch (pivot.y)
  124. {
  125. case 0: return SpriteAlignment.BottomCenter;
  126. case 0.5f: return SpriteAlignment.Center;
  127. case 1: return SpriteAlignment.TopCenter;
  128. }
  129. break;
  130. case 1:
  131. switch (pivot.y)
  132. {
  133. case 0: return SpriteAlignment.BottomRight;
  134. case 0.5f: return SpriteAlignment.RightCenter;
  135. case 1: return SpriteAlignment.TopRight;
  136. }
  137. break;
  138. }
  139. return SpriteAlignment.Custom;
  140. }
  141. /************************************************************************************************************************/
  142. private readonly TextureImporter Importer;
  143. /************************************************************************************************************************/
  144. /// <summary>Creates a new <see cref="SpriteDataEditor"/>.</summary>
  145. public SpriteDataEditor(TextureImporter importer)
  146. {
  147. Importer = importer;
  148. #if UNITY_2D_SPRITE
  149. Provider = Factories.GetSpriteEditorDataProviderFromObject(importer);
  150. Provider.InitSpriteEditorDataProvider();
  151. _SpriteRects = Provider.GetSpriteRects();
  152. #else
  153. _SpriteSheet = importer.spritesheet;
  154. #endif
  155. }
  156. /************************************************************************************************************************/
  157. /// <summary>Tries to find the index of the data matching the `sprite`.</summary>
  158. /// <remarks>
  159. /// Returns -1 if there is no data matching the <see cref="UnityEngine.Object.name"/>.
  160. /// <para></para>
  161. /// Returns -2 if there is more than one data matching the <see cref="UnityEngine.Object.name"/> but no
  162. /// <see cref="Sprite.rect"/> match.
  163. /// </remarks>
  164. public int IndexOf(Sprite sprite)
  165. {
  166. var nameMatchIndex = -1;
  167. var count = SpriteCount;
  168. for (int i = 0; i < count; i++)
  169. {
  170. if (GetName(i) == sprite.name)
  171. {
  172. if (GetRect(i) == sprite.rect)
  173. return i;
  174. if (nameMatchIndex == -1)// First name match.
  175. nameMatchIndex = i;
  176. else
  177. nameMatchIndex = -2;// Already found 2 name matches.
  178. }
  179. }
  180. if (nameMatchIndex == -1)
  181. {
  182. Debug.LogError($"No {nameof(SpriteMetaData)} for '{sprite.name}' was found.", sprite);
  183. }
  184. else if (nameMatchIndex == -2)
  185. {
  186. Debug.LogError($"More than one {nameof(SpriteMetaData)} for '{sprite.name}' was found" +
  187. $" but none of them matched the {nameof(Sprite)}.{nameof(Sprite.rect)}." +
  188. $" If the texture's Max Size is smaller than its actual size, increase the Max Size before performing this" +
  189. $" operation so that the {nameof(Rect)}s can be used to identify the correct data.", sprite);
  190. }
  191. return nameMatchIndex;
  192. }
  193. /************************************************************************************************************************/
  194. /// <summary>Logs an error and returns false if the data at the specified `index` is out of the texture bounds.</summary>
  195. public bool ValidateBounds(int index, Sprite sprite)
  196. {
  197. var rect = GetRect(index);
  198. if (rect.xMin >= 0 &&
  199. rect.yMin >= 0 &&
  200. rect.xMax <= sprite.texture.width &&
  201. rect.yMax <= sprite.texture.height)
  202. return true;
  203. var path = AssetDatabase.GetAssetPath(sprite);
  204. // The Max Texture Size import setting may cause the loaded texture to be smaller than the actual image.
  205. // Sprite dimensions are defined against the actual image though, so we need to check those bounds.
  206. var importer = (TextureImporter)AssetImporter.GetAtPath(path);
  207. importer.GetSourceTextureWidthAndHeight(out var width, out var height);
  208. if (rect.xMin >= 0 &&
  209. rect.yMin >= 0 &&
  210. rect.xMax <= width &&
  211. rect.yMax <= height)
  212. return true;
  213. Debug.LogError(
  214. $"This modification would put '{sprite.name}' at {rect}" +
  215. $" which is outside of the texture ({width}x{height})" +
  216. $" so '{path}' was not modified.",
  217. sprite);
  218. return false;
  219. }
  220. /************************************************************************************************************************/
  221. /// <summary>Applies any modifications to the target asset.</summary>
  222. public void Apply()
  223. {
  224. #if UNITY_2D_SPRITE
  225. Provider.SetSpriteRects(_SpriteRects);
  226. Provider.Apply();
  227. #else
  228. Importer.spritesheet = _SpriteSheet;
  229. EditorUtility.SetDirty(Importer);
  230. #endif
  231. Importer.SaveAndReimport();
  232. }
  233. /************************************************************************************************************************/
  234. }
  235. }
  236. #endif