Files
aibis-dream/Assets/Scripts/Effects/SpriteNoiseGlitchController.cs
T
2026-03-13 22:07:36 +08:00

229 lines
6.5 KiB
C#

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<SpriteRenderer>();
_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);
}
/// <summary>
/// 从当前强度平滑过渡到目标值(类似 DOTween),不瞬移。
/// </summary>
/// <param name="target">目标强度 [0,1]</param>
/// <param name="durationSeconds">过渡时长(秒)</param>
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);
}
}
/// <summary>
/// 从当前强度平滑过渡到目标值,等待完成后返回。供 Yarn 等需要阻塞的调用使用。
/// </summary>
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;
}
/// <summary>
/// 将 [0,1] 强度映射到对应阶段(用于音效匹配)
/// </summary>
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<AudioSource>();
if (_audioSource == null)
_audioSource = gameObject.AddComponent<AudioSource>();
}
_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<SpriteRenderer>();
if (_renderer != null && _mpb == null)
_mpb = new MaterialPropertyBlock();
ApplyIntensity();
}
#endif
}