123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
- #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
- using System.Collections.Generic;
- using UnityEngine;
- namespace Animancer
- {
- /// <summary>
- /// Replaces the <see cref="SpriteRenderer.sprite"/> with a copy of it that uses a different <see cref="Texture"/>
- /// during every <see cref="LateUpdate"/>.
- /// </summary>
- ///
- /// <remarks>This script is not specific to Animancer and will work with any animation system.</remarks>
- /// https://kybernetik.com.au/animancer/api/Animancer/SpriteRendererTextureSwap
- ///
- [AddComponentMenu("Animancer/Sprite Renderer Texture Swap")]
- [HelpURL("https://kybernetik.com.au/animancer/api/Animancer/" + nameof(SpriteRendererTextureSwap))]
- [DefaultExecutionOrder(DefaultExecutionOrder)]
- public class SpriteRendererTextureSwap : MonoBehaviour
- {
- /************************************************************************************************************************/
- /// <summary>Execute very late (32000 is last).</summary>
- public const int DefaultExecutionOrder = 30000;
- /************************************************************************************************************************/
- [SerializeField]
- [Tooltip("The SpriteRenderer that will have its Sprite modified")]
- private SpriteRenderer _Renderer;
- /// <summary>The <see cref="SpriteRenderer"/> that will have its <see cref="Sprite"/> modified.</summary>
- public ref SpriteRenderer Renderer => ref _Renderer;
- /************************************************************************************************************************/
- [SerializeField]
- [Tooltip("The replacement for the original Sprite texture")]
- private Texture2D _Texture;
- /// <summary>The replacement for the original <see cref="Sprite.texture"/>.</summary>
- /// <remarks>
- /// If this texture has any <see cref="Sprite"/>s set up in its import settings, they will be completely
- /// ignored because this system creates new <see cref="Sprite"/>s at runtime. The texture doesn't even need to
- /// be set to <see cref="Sprite"/> mode.
- /// <para></para>
- /// Call <see cref="ClearCache"/> before setting this if you want to destroy any sprites created for the
- /// previous texture.
- /// </remarks>
- public Texture2D Texture
- {
- get => _Texture;
- set
- {
- _Texture = value;
- RefreshSpriteMap();
- }
- }
- /************************************************************************************************************************/
- [SerializeField]
- [Tooltip("Should the secondary textures be swapped as well?")]
- private bool _SwapSecondaryTextures = true;
- /// <summary>Should the secondary textures be swapped as well?</summary>
- public bool SwapSecondaryTextures
- {
- get => _SwapSecondaryTextures;
- set
- {
- _SwapSecondaryTextures = value;
- RefreshSpriteMap();
- }
- }
- /************************************************************************************************************************/
- [SerializeField]
- [Tooltip("The replacement secondary textures for the Sprite to use")]
- private SecondarySpriteTexture[] _SecondaryTextures;
- /// <summary>The replacement for the original <see cref="Sprite.GetSecondaryTextures"/>.</summary>
- /// <remarks>
- /// Swapped sprites are cached statically so they can be shared between instances. Unfortunately, the cache
- /// isn't tied to secondary textures so if multiple instances replace TextureA with TextureB and have different
- /// secondary textures then they would interfere with each other. That's unlikely to be a real issue because
- /// TextureB should always have TextureBNormals as a secondary texture. It would be possible to avoid this
- /// if necessary, but doing so would cost more performance and increase the complexity of this system.
- /// </remarks>
- public SecondarySpriteTexture[] SecondaryTextures
- {
- get => _SecondaryTextures;
- set
- {
- _SecondaryTextures = value;
- RefreshSpriteMap();
- }
- }
- /************************************************************************************************************************/
- private Dictionary<Sprite, Sprite> _SpriteMap;
- private void RefreshSpriteMap() => _SpriteMap = GetSpriteMap(_Texture);
- /************************************************************************************************************************/
- protected virtual void OnValidate()
- {
- if (_Renderer == null)
- TryGetComponent(out _Renderer);
- if (_SwapSecondaryTextures &&
- (_SecondaryTextures == null || _SecondaryTextures.Length == 0) &&
- _Renderer != null &&
- _Renderer.sprite != null)
- {
- var sprite = _Renderer.sprite;
- var count = sprite.GetSecondaryTextureCount();
- if (count > 0)
- {
- _SecondaryTextures = new SecondarySpriteTexture[count];
- sprite.GetSecondaryTextures(_SecondaryTextures);
- }
- }
- if (_SpriteMap != null)
- DestroySprites(_SpriteMap);
- }
- /************************************************************************************************************************/
- protected virtual void Awake() => RefreshSpriteMap();
- /************************************************************************************************************************/
- protected virtual void LateUpdate()
- {
- if (_Renderer == null)
- return;
- var sprite = _Renderer.sprite;
- var secondaryTextures = _SwapSecondaryTextures ? _SecondaryTextures : null;
- if (TrySwapTexture(_SpriteMap, _Texture, secondaryTextures, ref sprite))
- _Renderer.sprite = sprite;
- }
- /************************************************************************************************************************/
- /// <summary>Destroys all sprites created for the current <see cref="Texture"/>.</summary>
- public void ClearCache()
- {
- DestroySprites(_SpriteMap);
- }
- /************************************************************************************************************************/
- private static readonly Dictionary<Texture2D, Dictionary<Sprite, Sprite>>
- TextureToSpriteMap = new();
- /************************************************************************************************************************/
- /// <summary>Returns a cached dictionary mapping original sprites to duplicates using the specified `texture`.</summary>
- public static Dictionary<Sprite, Sprite> GetSpriteMap(Texture2D texture)
- {
- if (texture == null)
- return null;
- if (!TextureToSpriteMap.TryGetValue(texture, out var map))
- TextureToSpriteMap.Add(texture, map = new());
- return map;
- }
- /************************************************************************************************************************/
- /// <summary>
- /// If the <see cref="Sprite.texture"/> is not already using the specified `texture`, this method replaces the
- /// `sprite` with a cached duplicate which uses that `texture` instead.
- /// </summary>
- public static bool TrySwapTexture(
- Dictionary<Sprite, Sprite> spriteMap,
- Texture2D texture,
- SecondarySpriteTexture[] secondaryTextures,
- ref Sprite sprite)
- {
- if (spriteMap == null ||
- sprite == null ||
- texture == null ||
- sprite.texture == texture)
- return false;
- if (!spriteMap.TryGetValue(sprite, out var otherSprite))
- {
- var pivot = sprite.pivot;
- pivot.x /= sprite.rect.width;
- pivot.y /= sprite.rect.height;
- secondaryTextures ??= GetSecondaryTexturesCached(sprite);
- otherSprite = Sprite.Create(texture,
- sprite.rect, pivot, sprite.pixelsPerUnit,
- 0, SpriteMeshType.FullRect, sprite.border, false, secondaryTextures);
- #if UNITY_ASSERTIONS
- var name = sprite.name;
- var originalTextureName = sprite.texture.name;
- var index = name.IndexOf(originalTextureName);
- if (index >= 0)
- {
- var newName =
- texture.name +
- name[(index + originalTextureName.Length)..];
- if (index > 0)
- newName = name[..index] + newName;
- name = newName;
- }
- otherSprite.name = name;
- #endif
- spriteMap.Add(sprite, otherSprite);
- }
- sprite = otherSprite;
- return true;
- }
- /************************************************************************************************************************/
- private static List<SecondarySpriteTexture[]> _SecondaryTextureCache;
- /// <summary>A wrapper around <see cref="Sprite.GetSecondaryTextures"/> which reuses arrays of the same size.</summary>
- public static SecondarySpriteTexture[] GetSecondaryTexturesCached(Sprite sprite)
- {
- var count = sprite.GetSecondaryTextureCount();
- if (count == 0)
- return System.Array.Empty<SecondarySpriteTexture>();
- _SecondaryTextureCache ??= new();
- while (_SecondaryTextureCache.Count < count)
- _SecondaryTextureCache.Add(null);
- var textures = _SecondaryTextureCache[count - 1];
- if (textures == null)
- {
- textures = new SecondarySpriteTexture[count];
- _SecondaryTextureCache[count - 1] = textures;
- }
- sprite.GetSecondaryTextures(textures);
- return textures;
- }
- /// <summary>A wrapper around <see cref="Sprite.GetSecondaryTextures"/>.</summary>
- public static SecondarySpriteTexture[] GetSecondaryTextures(Sprite sprite)
- {
- var count = sprite.GetSecondaryTextureCount();
- if (count == 0)
- return System.Array.Empty<SecondarySpriteTexture>();
- var textures = new SecondarySpriteTexture[count];
- sprite.GetSecondaryTextures(textures);
- return textures;
- }
- /************************************************************************************************************************/
- /// <summary>Destroys all the <see cref="Dictionary{TKey, TValue}.Values"/>.</summary>
- public static void DestroySprites(Dictionary<Sprite, Sprite> spriteMap)
- {
- if (spriteMap == null)
- return;
- foreach (var sprite in spriteMap.Values)
- Destroy(sprite);
- spriteMap.Clear();
- }
- /************************************************************************************************************************/
- /// <summary>Destroys all sprites created for the `texture`.</summary>
- public static void DestroySprites(Texture2D texture)
- {
- if (TextureToSpriteMap.TryGetValue(texture, out var spriteMap))
- {
- TextureToSpriteMap.Remove(texture);
- DestroySprites(spriteMap);
- }
- }
- /************************************************************************************************************************/
- }
- }
|