SpriteDataEditor.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
  2. #if UNITY_EDITOR
  3. using UnityEditor;
  4. using UnityEngine;
  5. using System;
  6. #if UNITY_2D_SPRITE
  7. using UnityEditor.U2D.Sprites;
  8. #endif
  9. namespace Animancer.Editor.Tools
  10. {
  11. /// <summary>A wrapper around the '2D Sprite' package features for editing Sprite data.</summary>
  12. public class SpriteDataEditor
  13. {
  14. /************************************************************************************************************************/
  15. /// <summary>Is the '2D Sprite' package currently in the project?</summary>
  16. public static bool Is2dPackagePresent;
  17. static SpriteDataEditor()
  18. {
  19. foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
  20. {
  21. if (assembly.GetName().Name == "Unity.2D.Sprite.Editor")
  22. {
  23. Is2dPackagePresent = true;
  24. break;
  25. }
  26. }
  27. }
  28. /************************************************************************************************************************/
  29. /// <summary>The scripting define symbol used to enable the features from the '2D Sprite' package.</summary>
  30. public const string SpritePackageDefineSymbol = "UNITY_2D_SPRITE";
  31. /// <summary>Is the <see cref="SpritePackageDefineSymbol"/> defined in the current platform?</summary>
  32. public static bool IsSpritePackageSymbolDefined
  33. {
  34. get => ScriptingDefineSymbols.IsSymbolDefined(SpritePackageDefineSymbol);
  35. set => ScriptingDefineSymbols.SetSymbolDefined(SpritePackageDefineSymbol, value);
  36. }
  37. /************************************************************************************************************************/
  38. #if UNITY_2D_SPRITE
  39. /************************************************************************************************************************/
  40. private static SpriteDataProviderFactories _Factories;
  41. private static SpriteDataProviderFactories Factories
  42. {
  43. get
  44. {
  45. if (_Factories == null)
  46. {
  47. _Factories = new SpriteDataProviderFactories();
  48. _Factories.Init();
  49. }
  50. return _Factories;
  51. }
  52. }
  53. /************************************************************************************************************************/
  54. private readonly ISpriteEditorDataProvider Provider;
  55. private readonly SpriteRect[] SpriteRects;
  56. /************************************************************************************************************************/
  57. /// <summary>The number of sprites in the target data.</summary>
  58. public int SpriteCount => SpriteRects.Length;
  59. /// <summary>Returns the name of the sprite at the specified `index`.</summary>
  60. public string GetName(int index) => SpriteRects[index].name;
  61. /// <summary>Sets the name of the sprite at the specified `index`.</summary>
  62. public void SetName(int index, string name) => SpriteRects[index].name = name;
  63. /// <summary>Returns the rect of the sprite at the specified `index`.</summary>
  64. public Rect GetRect(int index) => SpriteRects[index].rect;
  65. /// <summary>Sets the rect of the sprite at the specified `index`.</summary>
  66. public void SetRect(int index, Rect rect) => SpriteRects[index].rect = rect;
  67. /// <summary>Returns the pivot of the sprite at the specified `index`.</summary>
  68. public Vector2 GetPivot(int index) => SpriteRects[index].pivot;
  69. /// <summary>Sets the pivot of the sprite at the specified `index`.</summary>
  70. public void SetPivot(int index, Vector2 pivot) => SpriteRects[index].pivot = pivot;
  71. /// <summary>Returns the alignment of the sprite at the specified `index`.</summary>
  72. public SpriteAlignment GetAlignment(int index) => SpriteRects[index].alignment;
  73. /// <summary>Sets the alignment of the sprite at the specified `index`.</summary>
  74. public void SetAlignment(int index, SpriteAlignment alignment) => SpriteRects[index].alignment = alignment;
  75. /// <summary>Returns the border of the sprite at the specified `index`.</summary>
  76. public Vector4 GetBorder(int index) => SpriteRects[index].border;
  77. /// <summary>Sets the border of the sprite at the specified `index`.</summary>
  78. public void SetBorder(int index, Vector4 border) => SpriteRects[index].border = border;
  79. /************************************************************************************************************************/
  80. #else
  81. /************************************************************************************************************************/
  82. private readonly SpriteMetaData[] SpriteSheet;
  83. /************************************************************************************************************************/
  84. /// <summary>The number of sprites in the target data.</summary>
  85. public int SpriteCount => SpriteSheet.Length;
  86. /// <summary>Returns the name of the sprite at the specified `index`.</summary>
  87. public string GetName(int index) => SpriteSheet[index].name;
  88. /// <summary>Sets the name of the sprite at the specified `index`.</summary>
  89. public void SetName(int index, string name) => SpriteSheet[index].name = name;
  90. /// <summary>Returns the rect of the sprite at the specified `index`.</summary>
  91. public Rect GetRect(int index) => SpriteSheet[index].rect;
  92. /// <summary>Sets the rect of the sprite at the specified `index`.</summary>
  93. public void SetRect(int index, Rect rect) => SpriteSheet[index].rect = rect;
  94. /// <summary>Returns the pivot of the sprite at the specified `index`.</summary>
  95. public Vector2 GetPivot(int index) => SpriteSheet[index].pivot;
  96. /// <summary>Sets the pivot of the sprite at the specified `index`.</summary>
  97. public void SetPivot(int index, Vector2 pivot) => SpriteSheet[index].pivot = pivot;
  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. private readonly TextureImporter Importer;
  110. /************************************************************************************************************************/
  111. /// <summary>Creates a new <see cref="SpriteDataEditor"/>.</summary>
  112. public SpriteDataEditor(TextureImporter importer)
  113. {
  114. Importer = importer;
  115. #if UNITY_2D_SPRITE
  116. Provider = Factories.GetSpriteEditorDataProviderFromObject(importer);
  117. Provider.InitSpriteEditorDataProvider();
  118. SpriteRects = Provider.GetSpriteRects();
  119. #else
  120. SpriteSheet = importer.spritesheet;
  121. #endif
  122. }
  123. /************************************************************************************************************************/
  124. /// <summary>Tries to find the index of the data matching the `sprite`.</summary>
  125. /// <remarks>
  126. /// Returns -1 if there is no data matching the <see cref="UnityEngine.Object.name"/>.
  127. /// <para></para>
  128. /// Returns -2 if there is more than one data matching the <see cref="UnityEngine.Object.name"/> but no
  129. /// <see cref="Sprite.rect"/> match.
  130. /// </remarks>
  131. public int IndexOf(Sprite sprite)
  132. {
  133. var nameMatchIndex = -1;
  134. var count = SpriteCount;
  135. for (int i = 0; i < count; i++)
  136. {
  137. if (GetName(i) == sprite.name)
  138. {
  139. if (GetRect(i) == sprite.rect)
  140. return i;
  141. if (nameMatchIndex == -1)// First name match.
  142. nameMatchIndex = i;
  143. else
  144. nameMatchIndex = -2;// Already found 2 name matches.
  145. }
  146. }
  147. if (nameMatchIndex == -1)
  148. {
  149. Debug.LogError($"No {nameof(SpriteMetaData)} for '{sprite.name}' was found.", sprite);
  150. }
  151. else if (nameMatchIndex == -2)
  152. {
  153. Debug.LogError($"More than one {nameof(SpriteMetaData)} for '{sprite.name}' was found" +
  154. $" but none of them matched the {nameof(Sprite)}.{nameof(Sprite.rect)}." +
  155. $" If the texture's Max Size is smaller than its actual size, increase the Max Size before performing this" +
  156. $" operation so that the {nameof(Rect)}s can be used to identify the correct data.", sprite);
  157. }
  158. return nameMatchIndex;
  159. }
  160. /************************************************************************************************************************/
  161. /// <summary>Logs an error and returns false if the data at the specified `index` is out of the texture bounds.</summary>
  162. public bool ValidateBounds(int index, Sprite sprite)
  163. {
  164. var rect = GetRect(index);
  165. var widthScale = rect.width / sprite.rect.width;
  166. var heightScale = rect.height / sprite.rect.height;
  167. if (rect.xMin < 0 ||
  168. rect.yMin < 0 ||
  169. rect.xMax > sprite.texture.width * widthScale ||
  170. rect.yMax > sprite.texture.height * heightScale)
  171. {
  172. var path = AssetDatabase.GetAssetPath(sprite);
  173. Debug.LogError($"This modification would have put '{sprite.name}' out of bounds" +
  174. $" so '{path}' was not modified.", sprite);
  175. return false;
  176. }
  177. return true;
  178. }
  179. /************************************************************************************************************************/
  180. /// <summary>Applies any modifications to the target asset.</summary>
  181. public void Apply()
  182. {
  183. #if UNITY_2D_SPRITE
  184. Provider.SetSpriteRects(SpriteRects);
  185. Provider.Apply();
  186. #else
  187. Importer.spritesheet = SpriteSheet;
  188. EditorUtility.SetDirty(Importer);
  189. #endif
  190. Importer.SaveAndReimport();
  191. }
  192. /************************************************************************************************************************/
  193. }
  194. }
  195. #endif