SpriteRendererTextureSwap.cs 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
  2. #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. namespace Animancer
  6. {
  7. /// <summary>
  8. /// Replaces the <see cref="SpriteRenderer.sprite"/> with a copy of it that uses a different <see cref="Texture"/>
  9. /// during every <see cref="LateUpdate"/>.
  10. /// </summary>
  11. ///
  12. /// <remarks>
  13. /// This script is not specific to Animancer and will work with any animation system if you remove the
  14. /// [<see cref="AddComponentMenu"/>] and [<see cref="HelpURL"/>] attributes.
  15. /// </remarks>
  16. /// https://kybernetik.com.au/animancer/api/Animancer/SpriteRendererTextureSwap
  17. ///
  18. [AddComponentMenu(Strings.MenuPrefix + "Sprite Renderer Texture Swap")]
  19. [HelpURL(Strings.DocsURLs.APIDocumentation + "/" + nameof(SpriteRendererTextureSwap))]
  20. [DefaultExecutionOrder(DefaultExecutionOrder)]
  21. public class SpriteRendererTextureSwap : MonoBehaviour
  22. {
  23. /************************************************************************************************************************/
  24. /// <summary>Execute very late (32000 is last).</summary>
  25. public const int DefaultExecutionOrder = 30000;
  26. /************************************************************************************************************************/
  27. [SerializeField]
  28. [Tooltip("The SpriteRenderer that will have its Sprite modified")]
  29. private SpriteRenderer _Renderer;
  30. /// <summary>The <see cref="SpriteRenderer"/> that will have its <see cref="Sprite"/> modified.</summary>
  31. public ref SpriteRenderer Renderer => ref _Renderer;
  32. /************************************************************************************************************************/
  33. [SerializeField]
  34. [Tooltip("The replacement for the original Sprite texture")]
  35. private Texture2D _Texture;
  36. /// <summary>The replacement for the original <see cref="Sprite.texture"/>.</summary>
  37. /// <remarks>
  38. /// If this texture has any <see cref="Sprite"/>s set up in its import settings, they will be completely
  39. /// ignored because this system creates new <see cref="Sprite"/>s at runtime. The texture doesn't even need to
  40. /// be set to <see cref="Sprite"/> mode.
  41. /// <para></para>
  42. /// Call <see cref="ClearCache"/> before setting this if you want to destroy any sprites created for the
  43. /// previous texture.
  44. /// </remarks>
  45. public Texture2D Texture
  46. {
  47. get => _Texture;
  48. set
  49. {
  50. _Texture = value;
  51. RefreshSpriteMap();
  52. }
  53. }
  54. /************************************************************************************************************************/
  55. private Dictionary<Sprite, Sprite> _SpriteMap;
  56. private void RefreshSpriteMap() => _SpriteMap = GetSpriteMap(_Texture);
  57. /************************************************************************************************************************/
  58. protected virtual void Awake() => RefreshSpriteMap();
  59. protected virtual void OnValidate() => RefreshSpriteMap();
  60. /************************************************************************************************************************/
  61. protected virtual void LateUpdate()
  62. {
  63. if (_Renderer == null)
  64. return;
  65. var sprite = _Renderer.sprite;
  66. if (TrySwapTexture(_SpriteMap, _Texture, ref sprite))
  67. _Renderer.sprite = sprite;
  68. }
  69. /************************************************************************************************************************/
  70. /// <summary>Destroys all sprites created for the current <see cref="Texture"/>.</summary>
  71. public void ClearCache()
  72. {
  73. DestroySprites(_SpriteMap);
  74. }
  75. /************************************************************************************************************************/
  76. private static readonly Dictionary<Texture2D, Dictionary<Sprite, Sprite>>
  77. TextureToSpriteMap = new Dictionary<Texture2D, Dictionary<Sprite, Sprite>>();
  78. /************************************************************************************************************************/
  79. /// <summary>Returns a cached dictionary mapping original sprites to duplicates using the specified `texture`.</summary>
  80. public static Dictionary<Sprite, Sprite> GetSpriteMap(Texture2D texture)
  81. {
  82. if (texture == null)
  83. return null;
  84. if (!TextureToSpriteMap.TryGetValue(texture, out var map))
  85. TextureToSpriteMap.Add(texture, map = new Dictionary<Sprite, Sprite>());
  86. return map;
  87. }
  88. /************************************************************************************************************************/
  89. /// <summary>
  90. /// If the <see cref="Sprite.texture"/> is not already using the specified `texture`, this method replaces the
  91. /// `sprite` with a cached duplicate which uses that `texture` instead.
  92. /// </summary>
  93. public static bool TrySwapTexture(Dictionary<Sprite, Sprite> spriteMap, Texture2D texture, ref Sprite sprite)
  94. {
  95. if (spriteMap == null ||
  96. sprite == null ||
  97. texture == null ||
  98. sprite.texture == texture)
  99. return false;
  100. if (!spriteMap.TryGetValue(sprite, out var otherSprite))
  101. {
  102. var pivot = sprite.pivot;
  103. pivot.x /= sprite.rect.width;
  104. pivot.y /= sprite.rect.height;
  105. otherSprite = Sprite.Create(texture,
  106. sprite.rect, pivot, sprite.pixelsPerUnit,
  107. 0, SpriteMeshType.FullRect, sprite.border, false);
  108. #if UNITY_ASSERTIONS
  109. var name = sprite.name;
  110. var originalTextureName = sprite.texture.name;
  111. var index = name.IndexOf(originalTextureName);
  112. if (index >= 0)
  113. {
  114. var newName =
  115. texture.name +
  116. name.Substring(index + originalTextureName.Length, name.Length - (index + originalTextureName.Length));
  117. if (index > 0)
  118. newName = name.Substring(0, index) + newName;
  119. name = newName;
  120. }
  121. otherSprite.name = name;
  122. #endif
  123. spriteMap.Add(sprite, otherSprite);
  124. }
  125. sprite = otherSprite;
  126. return true;
  127. }
  128. /************************************************************************************************************************/
  129. /// <summary>Destroys all the <see cref="Dictionary{TKey, TValue}.Values"/>.</summary>
  130. public static void DestroySprites(Dictionary<Sprite, Sprite> spriteMap)
  131. {
  132. if (spriteMap == null)
  133. return;
  134. foreach (var sprite in spriteMap.Values)
  135. Destroy(sprite);
  136. spriteMap.Clear();
  137. }
  138. /************************************************************************************************************************/
  139. /// <summary>Destroys all sprites created for the `texture`.</summary>
  140. public static void DestroySprites(Texture2D texture)
  141. {
  142. if (TextureToSpriteMap.TryGetValue(texture, out var spriteMap))
  143. {
  144. TextureToSpriteMap.Remove(texture);
  145. DestroySprites(spriteMap);
  146. }
  147. }
  148. /************************************************************************************************************************/
  149. }
  150. }