1
This commit is contained in:
194
Assets/Plugins/Animancer/Utilities/SpriteRendererTextureSwap.cs
Normal file
194
Assets/Plugins/Animancer/Utilities/SpriteRendererTextureSwap.cs
Normal file
@@ -0,0 +1,194 @@
|
||||
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2023 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 if you remove the
|
||||
/// [<see cref="AddComponentMenu"/>] and [<see cref="HelpURL"/>] attributes.
|
||||
/// </remarks>
|
||||
/// https://kybernetik.com.au/animancer/api/Animancer/SpriteRendererTextureSwap
|
||||
///
|
||||
[AddComponentMenu(Strings.MenuPrefix + "Sprite Renderer Texture Swap")]
|
||||
[HelpURL(Strings.DocsURLs.APIDocumentation + "/" + 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();
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
private Dictionary<Sprite, Sprite> _SpriteMap;
|
||||
|
||||
private void RefreshSpriteMap() => _SpriteMap = GetSpriteMap(_Texture);
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
protected virtual void Awake() => RefreshSpriteMap();
|
||||
|
||||
protected virtual void OnValidate() => RefreshSpriteMap();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
protected virtual void LateUpdate()
|
||||
{
|
||||
if (_Renderer == null)
|
||||
return;
|
||||
|
||||
var sprite = _Renderer.sprite;
|
||||
if (TrySwapTexture(_SpriteMap, _Texture, 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 Dictionary<Texture2D, Dictionary<Sprite, Sprite>>();
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <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 Dictionary<Sprite, Sprite>());
|
||||
|
||||
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, 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;
|
||||
|
||||
otherSprite = Sprite.Create(texture,
|
||||
sprite.rect, pivot, sprite.pixelsPerUnit,
|
||||
0, SpriteMeshType.FullRect, sprite.border, false);
|
||||
|
||||
#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.Substring(index + originalTextureName.Length, name.Length - (index + originalTextureName.Length));
|
||||
|
||||
if (index > 0)
|
||||
newName = name.Substring(0, index) + newName;
|
||||
|
||||
name = newName;
|
||||
}
|
||||
|
||||
otherSprite.name = name;
|
||||
#endif
|
||||
|
||||
spriteMap.Add(sprite, otherSprite);
|
||||
}
|
||||
|
||||
sprite = otherSprite;
|
||||
return true;
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************************************************************/
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user