No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SpriteLibraryAsset.cs 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. using System;
  2. using System.Linq;
  3. using System.Collections.Generic;
  4. using UnityEditor;
  5. using UnityEngine.Assertions;
  6. using UnityEngine.Scripting.APIUpdating;
  7. namespace UnityEngine.U2D.Animation
  8. {
  9. internal interface INameHash
  10. {
  11. string name { get; set; }
  12. int hash { get; }
  13. }
  14. [Serializable]
  15. [MovedFrom("UnityEngine.Experimental.U2D.Animation")]
  16. internal class SpriteCategoryEntry : INameHash
  17. {
  18. [SerializeField]
  19. string m_Name;
  20. [SerializeField]
  21. [HideInInspector]
  22. int m_Hash;
  23. [SerializeField]
  24. Sprite m_Sprite;
  25. public string name
  26. {
  27. get => m_Name;
  28. set
  29. {
  30. m_Name = value;
  31. m_Hash = SpriteLibraryUtility.GetStringHash(m_Name);
  32. }
  33. }
  34. public int hash => m_Hash;
  35. public Sprite sprite
  36. {
  37. get => m_Sprite;
  38. set => m_Sprite = value;
  39. }
  40. public void UpdateHash()
  41. {
  42. m_Hash = SpriteLibraryUtility.GetStringHash(m_Name);
  43. }
  44. }
  45. [Serializable]
  46. [MovedFrom("UnityEngine.Experimental.U2D.Animation")]
  47. internal class SpriteLibCategory : INameHash
  48. {
  49. [SerializeField]
  50. string m_Name;
  51. [SerializeField]
  52. int m_Hash;
  53. [SerializeField]
  54. List<SpriteCategoryEntry> m_CategoryList;
  55. public string name
  56. {
  57. get { return m_Name; }
  58. set
  59. {
  60. m_Name = value;
  61. m_Hash = SpriteLibraryUtility.GetStringHash(m_Name);
  62. }
  63. }
  64. public int hash { get { return m_Hash; } }
  65. public List<SpriteCategoryEntry> categoryList
  66. {
  67. get => m_CategoryList;
  68. set => m_CategoryList = value;
  69. }
  70. public void UpdateHash()
  71. {
  72. m_Hash = SpriteLibraryUtility.GetStringHash(m_Name);
  73. foreach (var s in m_CategoryList)
  74. s.UpdateHash();
  75. }
  76. internal void ValidateLabels()
  77. {
  78. SpriteLibraryAsset.RenameDuplicate(m_CategoryList,
  79. (originalName, newName)
  80. =>
  81. {
  82. Debug.LogWarning(string.Format("Label {0} renamed to {1} due to hash clash", originalName, newName));
  83. });
  84. }
  85. }
  86. /// <summary>
  87. /// A custom Asset that stores Sprites grouping
  88. /// </summary>
  89. /// <Description>
  90. /// Sprites are grouped under a given category as categories. Each category and label needs to have
  91. /// a name specified so that it can be queried.
  92. /// </Description>
  93. [HelpURL("https://docs.unity3d.com/Packages/com.unity.2d.animation@latest/index.html?subfolder=/manual/SLAsset.html")]
  94. [MovedFrom("UnityEngine.Experimental.U2D.Animation")]
  95. public class SpriteLibraryAsset : ScriptableObject
  96. {
  97. [SerializeField]
  98. private List<SpriteLibCategory> m_Labels = new List<SpriteLibCategory>();
  99. [SerializeField]
  100. private long m_ModificationHash;
  101. internal static SpriteLibraryAsset CreateAsset(List<SpriteLibCategory> categories, string assetName, long version)
  102. {
  103. var asset = ScriptableObject.CreateInstance<SpriteLibraryAsset>();
  104. asset.m_Labels = categories;
  105. asset.ValidateCategories();
  106. asset.name = assetName;
  107. asset.UpdateHashes();
  108. asset.m_ModificationHash = version;
  109. return asset;
  110. }
  111. internal List<SpriteLibCategory> categories
  112. {
  113. get => m_Labels;
  114. set
  115. {
  116. m_Labels = value;
  117. ValidateCategories();
  118. UpdateModificationHash();
  119. }
  120. }
  121. /// <summary>
  122. /// Hash to quickly check if the library has any changes made to it.
  123. /// </summary>
  124. internal long modificationHash => m_ModificationHash;
  125. internal Sprite GetSprite(int categoryHash, int labelHash)
  126. {
  127. var category = m_Labels.FirstOrDefault(x => x.hash == categoryHash);
  128. if (category != null)
  129. {
  130. var spriteLabel = category.categoryList.FirstOrDefault(x => x.hash == labelHash);
  131. if (spriteLabel != null)
  132. {
  133. return spriteLabel.sprite;
  134. }
  135. }
  136. return null;
  137. }
  138. internal Sprite GetSprite(int categoryHash, int labelHash, out bool validEntry)
  139. {
  140. SpriteLibCategory category = null;
  141. for (int i = 0; i < m_Labels.Count; ++i)
  142. {
  143. if (m_Labels[i].hash == categoryHash)
  144. {
  145. category = m_Labels[i];
  146. break;
  147. }
  148. }
  149. if (category != null)
  150. {
  151. SpriteCategoryEntry spritelabel = null;
  152. for (int i = 0; i < category.categoryList.Count; ++i)
  153. {
  154. if (category.categoryList[i].hash == labelHash)
  155. {
  156. spritelabel = category.categoryList[i];
  157. break;
  158. }
  159. }
  160. if (spritelabel != null)
  161. {
  162. validEntry = true;
  163. return spritelabel.sprite;
  164. }
  165. }
  166. validEntry = false;
  167. return null;
  168. }
  169. /// <summary>
  170. /// Returns the Sprite registered in the Asset given the Category and Label value
  171. /// </summary>
  172. /// <param name="category">Category string value</param>
  173. /// <param name="label">Label string value</param>
  174. /// <returns></returns>
  175. public Sprite GetSprite(string category, string label)
  176. {
  177. var categoryHash = SpriteLibraryUtility.GetStringHash(category);
  178. var labelHash = SpriteLibraryUtility.GetStringHash(label);
  179. return GetSprite(categoryHash, labelHash);
  180. }
  181. /// <summary>
  182. /// Return all the Category names of the Sprite Library Asset that is associated.
  183. /// </summary>
  184. /// <returns>A Enumerable string value representing the name</returns>
  185. public IEnumerable<string> GetCategoryNames()
  186. {
  187. return m_Labels.Select(x => x.name);
  188. }
  189. /// <summary>
  190. /// (Obsolete) Returns the labels' name for the given name
  191. /// </summary>
  192. /// <param name="category">Category name</param>
  193. /// <returns>A Enumerable string representing labels' name</returns>
  194. [Obsolete("GetCategorylabelNames has been deprecated. Please use GetCategoryLabelNames (UnityUpgradable) -> GetCategoryLabelNames(*)")]
  195. public IEnumerable<string> GetCategorylabelNames(string category)
  196. {
  197. return GetCategoryLabelNames(category);
  198. }
  199. /// <summary>
  200. /// Returns the labels' name for the given name
  201. /// </summary>
  202. /// <param name="category">Category name</param>
  203. /// <returns>A Enumerable string representing labels' name</returns>
  204. public IEnumerable<string> GetCategoryLabelNames(string category)
  205. {
  206. var label = m_Labels.FirstOrDefault(x => x.name == category);
  207. return label == null ? new string[0] : label.categoryList.Select(x => x.name);
  208. }
  209. /// <summary>
  210. /// Add or replace and existing Sprite into the given Category and Label
  211. /// </summary>
  212. /// <param name="sprite">Sprite to add</param>
  213. /// <param name="category">Category to add the Sprite to</param>
  214. /// <param name="label">Label of the Category to add the Sprite to. If this parameter is null or an empty string, it will attempt to add a empty category</param>
  215. public void AddCategoryLabel(Sprite sprite, string category, string label)
  216. {
  217. category = category.Trim();
  218. label = label?.Trim();
  219. if (string.IsNullOrEmpty(category))
  220. throw new ArgumentException("Cannot add empty or null Category string");
  221. var catHash = SpriteLibraryUtility.GetStringHash(category);
  222. SpriteCategoryEntry categorylabel = null;
  223. SpriteLibCategory libCategory = null;
  224. libCategory = m_Labels.FirstOrDefault(x => x.hash == catHash);
  225. if (libCategory != null)
  226. {
  227. if(string.IsNullOrEmpty(label))
  228. throw new ArgumentException("Cannot add empty or null Label string");
  229. Assert.AreEqual(libCategory.name, category, "Category string hash clashes with another existing Category. Please use another string");
  230. var labelHash = SpriteLibraryUtility.GetStringHash(label);
  231. categorylabel = libCategory.categoryList.FirstOrDefault(y => y.hash == labelHash);
  232. if (categorylabel != null)
  233. {
  234. Assert.AreEqual(categorylabel.name, label, "Label string hash clashes with another existing label. Please use another string");
  235. categorylabel.sprite = sprite;
  236. }
  237. else
  238. {
  239. categorylabel = new SpriteCategoryEntry()
  240. {
  241. name = label,
  242. sprite = sprite
  243. };
  244. libCategory.categoryList.Add(categorylabel);
  245. }
  246. }
  247. else
  248. {
  249. var slc = new SpriteLibCategory()
  250. {
  251. categoryList = new List<SpriteCategoryEntry>(),
  252. name = category
  253. };
  254. if (!string.IsNullOrEmpty(label))
  255. {
  256. slc.categoryList.Add(new SpriteCategoryEntry()
  257. {
  258. name = label,
  259. sprite = sprite
  260. });
  261. }
  262. m_Labels.Add(slc);
  263. }
  264. UpdateModificationHash();
  265. #if UNITY_EDITOR
  266. EditorUtility.SetDirty(this);
  267. #endif
  268. }
  269. /// <summary>
  270. /// Remove a Label from a given Category
  271. /// </summary>
  272. /// <param name="category">Category to remove from</param>
  273. /// <param name="label">Label to remove</param>
  274. /// <param name="deleteCategory">Indicate to remove the Category if it is empty</param>
  275. public void RemoveCategoryLabel(string category, string label, bool deleteCategory)
  276. {
  277. var catHash = SpriteLibraryUtility.GetStringHash(category);
  278. SpriteLibCategory libCategory = null;
  279. libCategory = m_Labels.FirstOrDefault(x => x.hash == catHash);
  280. if (libCategory != null)
  281. {
  282. var labelHash = SpriteLibraryUtility.GetStringHash(label);
  283. libCategory.categoryList.RemoveAll(x => x.hash == labelHash);
  284. if (deleteCategory && libCategory.categoryList.Count == 0)
  285. m_Labels.RemoveAll(x => x.hash == libCategory.hash);
  286. UpdateModificationHash();
  287. #if UNITY_EDITOR
  288. EditorUtility.SetDirty(this);
  289. #endif
  290. }
  291. }
  292. internal void UpdateHashes()
  293. {
  294. foreach (var e in m_Labels)
  295. e.UpdateHash();
  296. #if UNITY_EDITOR
  297. UnityEditor.EditorUtility.SetDirty(this);
  298. #endif
  299. }
  300. internal void ValidateCategories()
  301. {
  302. RenameDuplicate(m_Labels, (originalName, newName)
  303. =>
  304. {
  305. Debug.LogWarning($"Category {originalName} renamed to {newName} due to hash clash");
  306. });
  307. for (var i = 0; i < m_Labels.Count; ++i)
  308. {
  309. // Verify categories have no hash clash
  310. var category = m_Labels[i];
  311. // Verify labels have no clash
  312. category.ValidateLabels();
  313. }
  314. }
  315. internal static void RenameDuplicate(IEnumerable<INameHash> nameHashList, Action<string, string> onRename)
  316. {
  317. const int k_IncrementMax = 1000;
  318. for (var i = 0; i < nameHashList.Count(); ++i)
  319. {
  320. // Verify categories have no hash clash
  321. var category = nameHashList.ElementAt(i);
  322. var categoriesClash = nameHashList.Where(x => (x.hash == category.hash || x.name == category.name) && x != category);
  323. int increment = 0;
  324. for (int j = 0; j < categoriesClash.Count(); ++j)
  325. {
  326. var categoryClash = categoriesClash.ElementAt(j);
  327. while (increment < k_IncrementMax)
  328. {
  329. var name = categoryClash.name;
  330. name = $"{name}_{increment}";
  331. var nameHash = SpriteLibraryUtility.GetStringHash(name);
  332. var exist = nameHashList.FirstOrDefault(x => (x.hash == nameHash || x.name == name) && x != categoryClash);
  333. if (exist == null)
  334. {
  335. onRename(categoryClash.name, name);
  336. categoryClash.name = name;
  337. break;
  338. }
  339. ++increment;
  340. }
  341. }
  342. }
  343. }
  344. void UpdateModificationHash()
  345. {
  346. var hash = System.DateTime.Now.Ticks;
  347. hash ^= m_Labels.GetHashCode();
  348. m_ModificationHash = hash;
  349. }
  350. }
  351. }