229 lines
6.5 KiB
C#
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
|
|
}
|