using UnityEngine; using System.Collections; [RequireComponent(typeof(SpriteRenderer))] public class SpriteNoiseGlitchController : MonoBehaviour { public enum GlitchStage { None, Light, Medium, Heavy } [Header("Runtime")] [SerializeField] private GlitchStage _currentStage = GlitchStage.None; [SerializeField, Range(0f, 1f)] private float _intensity; [SerializeField] private float _transitionDuration = 0.5f; [Header("Audio (Optional)")] [Tooltip("由 Tools > Generate Glitch Noise Audio 生成")] [SerializeField] private AudioClip _stage1Clip; [SerializeField] private AudioClip _stage2Clip; [SerializeField] private AudioClip _stage3Clip; [SerializeField, Range(0f, 1f)] private float _audioVolume = 0.5f; private SpriteRenderer _renderer; private AudioSource _audioSource; private MaterialPropertyBlock _mpb; private Coroutine _transitionCoroutine; private static readonly int PropGlitchIntensity = Shader.PropertyToID("_GlitchIntensity"); private static readonly float[] StageValues = { 0f, 0.25f, 0.55f, 0.95f }; public float Intensity { get => _intensity; set { _intensity = Mathf.Clamp01(value); ApplyIntensity(); } } public GlitchStage CurrentStage { get => _currentStage; set => SetStage(value); } private void Awake() { _renderer = GetComponent(); _mpb = new MaterialPropertyBlock(); } private void OnEnable() { ApplyIntensity(); } public void SetStage(GlitchStage stage, bool instant = false) { _currentStage = stage; float target = StageValues[(int)stage]; ApplyAudio(stage); if (instant || !gameObject.activeInHierarchy) { Intensity = target; return; } if (_transitionCoroutine != null) StopCoroutine(_transitionCoroutine); _transitionCoroutine = StartCoroutine(TransitionTo(target)); } public void SetStageByIndex(int index) { index = Mathf.Clamp(index, 0, 3); SetStage((GlitchStage)index); } /// /// 从当前强度平滑过渡到目标值(类似 DOTween),不瞬移。 /// /// 目标强度 [0,1] /// 过渡时长(秒) public void TransitionTo(float target, float durationSeconds) { if (durationSeconds > 0f && gameObject.activeInHierarchy) StartCoroutine(TransitionToAndWait(target, durationSeconds)); else { target = Mathf.Clamp01(target); Intensity = target; _currentStage = ValueToStage(target); ApplyAudio(_currentStage); } } /// /// 从当前强度平滑过渡到目标值,等待完成后返回。供 Yarn 等需要阻塞的调用使用。 /// public IEnumerator TransitionToAndWait(float target, float durationSeconds) { target = Mathf.Clamp01(target); float startValue = _intensity; if (_transitionCoroutine != null) StopCoroutine(_transitionCoroutine); if (durationSeconds <= 0f || !gameObject.activeInHierarchy) { Intensity = target; _currentStage = ValueToStage(target); ApplyAudio(_currentStage); yield break; } _transitionCoroutine = StartCoroutine(TransitionToValue(startValue, target, durationSeconds)); yield return _transitionCoroutine; _transitionCoroutine = null; } /// /// 将 [0,1] 强度映射到对应阶段(用于音效匹配) /// private static GlitchStage ValueToStage(float value) { if (value < 0.125f) return GlitchStage.None; if (value < 0.4f) return GlitchStage.Light; if (value < 0.75f) return GlitchStage.Medium; return GlitchStage.Heavy; } private IEnumerator TransitionTo(float target) { float start = _intensity; float elapsed = 0f; while (elapsed < _transitionDuration) { elapsed += Time.deltaTime; float t = Mathf.SmoothStep(0f, 1f, elapsed / _transitionDuration); Intensity = Mathf.Lerp(start, target, t); yield return null; } Intensity = target; _transitionCoroutine = null; } private IEnumerator TransitionToValue(float from, float to, float duration) { float elapsed = 0f; while (elapsed < duration) { elapsed += Time.deltaTime; float t = Mathf.SmoothStep(0f, 1f, elapsed / duration); float current = Mathf.Lerp(from, to, t); Intensity = current; // 过渡过程中,若跨越阶段边界则切换对应音效 var stage = ValueToStage(current); if (stage != _currentStage) { _currentStage = stage; ApplyAudio(stage); } yield return null; } Intensity = to; _currentStage = ValueToStage(to); ApplyAudio(_currentStage); _transitionCoroutine = null; } private void ApplyIntensity() { if (_renderer == null) return; _renderer.GetPropertyBlock(_mpb); _mpb.SetFloat(PropGlitchIntensity, _intensity); _renderer.SetPropertyBlock(_mpb); } private void ApplyAudio(GlitchStage stage) { if (_stage1Clip == null && _stage2Clip == null && _stage3Clip == null) return; if (_audioSource == null) { _audioSource = GetComponent(); if (_audioSource == null) _audioSource = gameObject.AddComponent(); } _audioSource.Stop(); AudioClip clip = stage switch { GlitchStage.Light => _stage1Clip, GlitchStage.Medium => _stage2Clip, GlitchStage.Heavy => _stage3Clip, _ => null }; if (clip != null) { _audioSource.clip = clip; _audioSource.volume = _audioVolume; _audioSource.loop = true; _audioSource.Play(); } } #if UNITY_EDITOR private void OnValidate() { if (_renderer == null) _renderer = GetComponent(); if (_renderer != null && _mpb == null) _mpb = new MaterialPropertyBlock(); ApplyIntensity(); } #endif }