| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using TMPro;
- using UnityEngine;
- using UnityEngine.UI;
- namespace AppUI.Util
- {
- [Serializable]
- public class UIBlinkTextItem
- {
- [Tooltip("留空则忽略")]
- public TMP_Text text;
- [Tooltip("勾选后使用下方颜色,否则用组件全局文字颜色")]
- public bool useCustomColors;
- public Color colorA = Color.white;
- public Color colorB = Color.red;
- }
- public enum UIBlinkImageMode
- {
- /// <summary>在 spriteA / spriteB 之间切换。</summary>
- Sprite,
- /// <summary>在 colorA / colorB 之间切换(不改 Sprite)。</summary>
- Color,
- }
- [Serializable]
- public class UIBlinkImageItem
- {
- [Tooltip("留空则忽略")]
- public Image image;
- public UIBlinkImageMode mode = UIBlinkImageMode.Sprite;
- [Tooltip("勾选后使用本项 Sprite,否则用组件全局图片")]
- public bool useCustomSprites;
- public Sprite spriteA;
- public Sprite spriteB;
- [Tooltip("Color 模式:勾选后用本项颜色,否则用组件全局图片颜色")]
- public bool useCustomColors;
- public Color colorA = Color.white;
- public Color colorB = new Color(1f, 1f, 1f, 0.35f);
- }
- /// <summary>
- /// 文字(TMP)闪烁:切换颜色;图片闪烁:切换 Sprite 或颜色。
- /// 可选平滑渐变过渡(颜色插值;Sprite 模式在过渡中点切换贴图并插值 Tint)。
- /// </summary>
- [DisallowMultipleComponent]
- public class UIBlinkEffect : MonoBehaviour
- {
- [Header("播放")]
- [Tooltip("每个状态保持时长(秒);开启渐变时指到达目标状态后的停留时间")]
- [SerializeField]
- [Min(0.02f)]
- float stateInterval = 0.5f;
- [SerializeField]
- bool loop = true;
- [SerializeField]
- bool playOnEnable = true;
- [Tooltip("停止时是否恢复到状态 A")]
- [SerializeField]
- bool restoreStateOnStop = true;
- [Tooltip("启用后延迟多久开始(秒)")]
- [SerializeField]
- [Min(0f)]
- float startDelay;
- [Header("渐变过渡(可选)")]
- [Tooltip("开启后 A↔B 之间颜色/透明度平滑插值,而非瞬间切换")]
- [SerializeField]
- bool useSmoothTransition;
- [Tooltip("单次 A→B 或 B→A 的过渡时长(秒)")]
- [SerializeField]
- [Min(0f)]
- float transitionDuration = 0.2f;
- [SerializeField]
- AnimationCurve transitionCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
- [Tooltip("勾选后过渡不受 Time.timeScale 影响")]
- [SerializeField]
- bool useUnscaledTime;
- [Header("文字(全局默认颜色)")]
- [SerializeField]
- Color textColorA = Color.white;
- [SerializeField]
- Color textColorB = Color.red;
- [SerializeField]
- List<UIBlinkTextItem> textItems = new List<UIBlinkTextItem>();
- [Header("图片")]
- [SerializeField]
- UIBlinkImageMode defaultImageMode = UIBlinkImageMode.Sprite;
- [SerializeField]
- Sprite imageSpriteA;
- [SerializeField]
- Sprite imageSpriteB;
- [SerializeField]
- Color imageColorA = Color.white;
- [SerializeField]
- Color imageColorB = new Color(1f, 1f, 1f, 0.35f);
- [SerializeField]
- List<UIBlinkImageItem> imageItems = new List<UIBlinkImageItem>();
- Coroutine _blinkRoutine;
- bool _showingStateA = true;
- bool _isPlaying;
- public bool IsPlaying => _isPlaying;
- public bool UseSmoothTransition
- {
- get => useSmoothTransition;
- set => useSmoothTransition = value;
- }
- public float TransitionDuration
- {
- get => transitionDuration;
- set => transitionDuration = Mathf.Max(0f, value);
- }
- public float StateInterval
- {
- get => stateInterval;
- set => stateInterval = Mathf.Max(0.02f, value);
- }
- public bool Loop
- {
- get => loop;
- set => loop = value;
- }
- void OnEnable()
- {
- if (playOnEnable)
- Play();
- }
- void OnDisable()
- {
- Stop(restoreStateOnStop);
- }
- public void Play()
- {
- Stop(false);
- _showingStateA = true;
- ApplyStateLerp(0f);
- _blinkRoutine = StartCoroutine(BlinkRoutine());
- _isPlaying = true;
- }
- public void Stop() => Stop(restoreStateOnStop);
- public void Stop(bool restore)
- {
- if (_blinkRoutine != null)
- {
- StopCoroutine(_blinkRoutine);
- _blinkRoutine = null;
- }
- _isPlaying = false;
- if (restore)
- ApplyStateLerp(0f);
- }
- public void SetState(bool stateA)
- {
- _showingStateA = stateA;
- ApplyStateLerp(stateA ? 0f : 1f);
- }
- IEnumerator BlinkRoutine()
- {
- if (startDelay > 0f)
- yield return WaitSeconds(startDelay);
- _showingStateA = true;
- ApplyStateLerp(0f);
- while (true)
- {
- yield return WaitSeconds(stateInterval);
- var targetA = !_showingStateA;
- yield return TransitionToState(targetA);
- _showingStateA = targetA;
- if (!loop)
- {
- if (!_showingStateA)
- {
- yield return WaitSeconds(stateInterval);
- yield return TransitionToState(true);
- _showingStateA = true;
- }
- _isPlaying = false;
- _blinkRoutine = null;
- yield break;
- }
- }
- }
- IEnumerator TransitionToState(bool targetStateA)
- {
- if (!useSmoothTransition || transitionDuration <= 0f)
- {
- ApplyStateLerp(targetStateA ? 0f : 1f);
- yield break;
- }
- var startT = _showingStateA ? 0f : 1f;
- var endT = targetStateA ? 0f : 1f;
- var elapsed = 0f;
- while (elapsed < transitionDuration)
- {
- elapsed += useUnscaledTime ? Time.unscaledDeltaTime : Time.deltaTime;
- var normalized = Mathf.Clamp01(elapsed / transitionDuration);
- var curveT = transitionCurve != null && transitionCurve.length > 0
- ? transitionCurve.Evaluate(normalized)
- : normalized;
- ApplyStateLerp(Mathf.Lerp(startT, endT, curveT));
- yield return null;
- }
- ApplyStateLerp(endT);
- }
- IEnumerator WaitSeconds(float seconds)
- {
- if (seconds <= 0f)
- yield break;
- if (useUnscaledTime)
- {
- var end = Time.unscaledTime + seconds;
- while (Time.unscaledTime < end)
- yield return null;
- }
- else
- {
- yield return new WaitForSeconds(seconds);
- }
- }
- /// <param name="t">0 = 状态 A,1 = 状态 B</param>
- void ApplyStateLerp(float t)
- {
- t = Mathf.Clamp01(t);
- ApplyTextStateLerp(t);
- ApplyImageStateLerp(t);
- }
- void ApplyTextStateLerp(float t)
- {
- if (textItems == null)
- return;
- foreach (var item in textItems)
- {
- if (item?.text == null)
- continue;
- var a = item.useCustomColors ? item.colorA : textColorA;
- var b = item.useCustomColors ? item.colorB : textColorB;
- item.text.color = Color.Lerp(a, b, t);
- }
- }
- void ApplyImageStateLerp(float t)
- {
- if (imageItems == null)
- return;
- foreach (var item in imageItems)
- {
- if (item?.image == null)
- continue;
- var mode = item.mode;
- if (mode == UIBlinkImageMode.Sprite && item.image.sprite == null && defaultImageMode == UIBlinkImageMode.Color)
- mode = UIBlinkImageMode.Color;
- var ca = item.useCustomColors ? item.colorA : imageColorA;
- var cb = item.useCustomColors ? item.colorB : imageColorB;
- if (mode == UIBlinkImageMode.Color)
- {
- item.image.color = Color.Lerp(ca, cb, t);
- continue;
- }
- var spA = item.useCustomSprites ? item.spriteA : imageSpriteA;
- var spB = item.useCustomSprites ? item.spriteB : imageSpriteB;
- if (useSmoothTransition && transitionDuration > 0f)
- {
- var sp = t < 0.5f ? spA : spB;
- if (sp != null)
- item.image.sprite = sp;
- item.image.color = Color.Lerp(ca, cb, t);
- }
- else
- {
- var sp = t < 0.5f ? spA : spB;
- if (sp != null)
- item.image.sprite = sp;
- item.image.color = Color.Lerp(ca, cb, t);
- }
- }
- }
- #if UNITY_EDITOR
- [ContextMenu("Preview State A")]
- void EditorPreviewA()
- {
- ApplyStateLerp(0f);
- }
- [ContextMenu("Preview State B")]
- void EditorPreviewB()
- {
- ApplyStateLerp(1f);
- }
- [ContextMenu("Preview Transition 50%")]
- void EditorPreviewMid()
- {
- ApplyStateLerp(0.5f);
- }
- #endif
- }
- }
|