using System.Collections; using UnityEngine; using UnityEngine.UI; [RequireComponent(typeof(Image))] public class idolMaterialController : MonoBehaviour { public enum SweepSide { FromLeft = 0, FromRight = 1, } [Header("Material")] public Material materialTemplate; [Header("Animation")] public SweepSide enterDirection = SweepSide.FromLeft; public SweepSide exitDirection = SweepSide.FromRight; public float enterDuration = 0.35f; public float exitDuration = 0.25f; [Range(0f, 1f)] public float hiddenAmount = 0f; [Range(0f, 1f)] public float shownAmount = 1f; public bool playEnterOnEnable = false; private Image image; private Material runtimeMaterial; private Coroutine transitionCoroutine; private Sprite pendingSprite; private static readonly int DissolveAmountId = Shader.PropertyToID("_DissolveAmount"); private static readonly int DirectionId = Shader.PropertyToID("_Direction"); private void Awake() { image = GetComponent(); EnsureRuntimeMaterial(); ApplyInstantState(shownAmount, GetShaderDirectionForEnter(enterDirection)); } private void OnEnable() { EnsureRuntimeMaterial(); if (playEnterOnEnable && image != null && image.sprite != null) { PlayEnter(); } else { ApplyInstantState(shownAmount, GetShaderDirectionForEnter(enterDirection)); } } private void OnDisable() { if (transitionCoroutine != null) { StopCoroutine(transitionCoroutine); transitionCoroutine = null; } } private void OnDestroy() { if (runtimeMaterial != null) { if (image != null && image.material == runtimeMaterial) { image.material = null; } Destroy(runtimeMaterial); } } public void SetSpriteImmediate(Sprite sprite) { EnsureRuntimeMaterial(); if (image == null) { return; } image.sprite = sprite; ApplyInstantState(shownAmount, GetShaderDirectionForEnter(enterDirection)); } public void PlayEnter() { EnsureRuntimeMaterial(); if (image == null || runtimeMaterial == null) { return; } if (transitionCoroutine != null) { StopCoroutine(transitionCoroutine); } transitionCoroutine = StartCoroutine(AnimateAmount(hiddenAmount, shownAmount, Mathf.Max(0.01f, enterDuration), GetShaderDirectionForEnter(enterDirection), null)); } public void PlayExit() { EnsureRuntimeMaterial(); if (image == null || runtimeMaterial == null) { return; } if (transitionCoroutine != null) { StopCoroutine(transitionCoroutine); } transitionCoroutine = StartCoroutine(AnimateAmount(shownAmount, hiddenAmount, Mathf.Max(0.01f, exitDuration), GetShaderDirectionForExit(exitDirection), null)); } public void TransitionToSprite(Sprite sprite) { EnsureRuntimeMaterial(); if (image == null) { return; } pendingSprite = sprite; if (transitionCoroutine != null) { StopCoroutine(transitionCoroutine); } bool hasCurrentSprite = image.sprite != null; bool spriteChanged = image.sprite != sprite; transitionCoroutine = StartCoroutine(TransitionRoutine(hasCurrentSprite, spriteChanged)); } private IEnumerator TransitionRoutine(bool hasCurrentSprite, bool spriteChanged) { if (runtimeMaterial == null || image == null) { yield break; } if (hasCurrentSprite && spriteChanged) { yield return AnimateAmount(shownAmount, hiddenAmount, Mathf.Max(0.01f, exitDuration), GetShaderDirectionForExit(exitDirection), null); } image.sprite = pendingSprite; if (pendingSprite == null) { ApplyInstantState(hiddenAmount, GetShaderDirectionForEnter(enterDirection)); transitionCoroutine = null; yield break; } yield return AnimateAmount(hiddenAmount, shownAmount, Mathf.Max(0.01f, enterDuration), GetShaderDirectionForEnter(enterDirection), null); transitionCoroutine = null; } private IEnumerator AnimateAmount(float from, float to, float duration, float directionValue, System.Action onComplete) { if (runtimeMaterial == null) { yield break; } runtimeMaterial.SetFloat(DirectionId, directionValue); runtimeMaterial.SetFloat(DissolveAmountId, from); float elapsed = 0f; while (elapsed < duration) { elapsed += Time.unscaledDeltaTime; float t = Mathf.Clamp01(elapsed / duration); runtimeMaterial.SetFloat(DissolveAmountId, Mathf.Lerp(from, to, t)); yield return null; } runtimeMaterial.SetFloat(DissolveAmountId, to); onComplete?.Invoke(); } private void ApplyInstantState(float amount, float directionValue) { EnsureRuntimeMaterial(); if (runtimeMaterial == null) { return; } runtimeMaterial.SetFloat(DirectionId, directionValue); runtimeMaterial.SetFloat(DissolveAmountId, Mathf.Clamp01(amount)); } private void EnsureRuntimeMaterial() { if (image == null) { image = GetComponent(); } if (image == null) { return; } Material source = materialTemplate != null ? materialTemplate : image.material; if (source == null) { return; } if (runtimeMaterial != null && runtimeMaterial.shader == source.shader) { if (image.material != runtimeMaterial) { image.material = runtimeMaterial; } return; } if (runtimeMaterial != null) { Destroy(runtimeMaterial); } runtimeMaterial = new Material(source); runtimeMaterial.name = source.name + " (idol runtime)"; image.material = runtimeMaterial; } private static float GetShaderDirectionForEnter(SweepSide side) { return side == SweepSide.FromLeft ? 0f : 1f; } private static float GetShaderDirectionForExit(SweepSide side) { return side == SweepSide.FromLeft ? 1f : 0f; } }