Files
aibis-dream/Assets/Scripts/MiniGame/HuoShan/EmotionWave/EmotionWaveController.cs
T
bottlefish 17994d4b56 feat(emotionwave): empty 待机态、预设切换过渡、noiseOnlyBlend、渲染优化
empty 改为极弱噪波待机态;增加预设切换 crossfade 与 flash 效果;
noiseOnlyBlend 支持纯噪波渲染;两段式归一化使 fillAmount=1 正确填满;switch_emotion_wave_config 支持 progress 参数。

Made-with: Cursor
2026-03-12 15:26:08 +08:00

192 lines
7.1 KiB
C#

using UnityEngine;
namespace AibisDream.MiniGame.HuoShan.EmotionWave
{
/// <summary>
/// Controls the EmotionWave visualization.
/// Drag the progress slider in the Inspector to drive the 3-act narrative.
/// Ported from Web_EmotionWave/js/app.js.
/// </summary>
[ExecuteAlways]
public class EmotionWaveController : MonoBehaviour
{
[Header("References")]
[SerializeField] private EmotionWaveConfig config;
[SerializeField] private EmotionWaveRenderer waveRenderer;
[Header("Preset")]
[Tooltip("当前波形预设:normal / predicted / modified_joke / empty,可由 switch_emotion_wave_config 切换")]
[SerializeField] private string presetId = "normal";
[Header("Preset Transition")]
[SerializeField] private float presetTransitionDuration = 0.22f;
[SerializeField] private float presetFlashDuration = 0.12f;
[SerializeField] private float presetFlashGlowBoost = 0.45f;
[SerializeField] private float presetFlashLightnessBoost = 18f;
[SerializeField] private float presetFlashGlitchBoost = 0.28f;
[Header("Progress Control")]
[Range(0f, 1f)]
[SerializeField] private float progress = 0f;
[Header("Smooth Tracking")]
[SerializeField] private float lerpSpeed = 4f;
[Header("CRT Startup")]
[SerializeField] private float startupDuration = 1.8f;
[Header("Debug")]
[SerializeField] [ReadOnlyField] private string currentStage = "";
[SerializeField] [ReadOnlyField] private string currentStageName = "";
[SerializeField] [ReadOnlyField] private float smoothedProgress;
private float _currentValue;
private float _elapsedTime;
private string _previousPresetId = "normal";
private float _presetTransitionElapsed = 999f;
private bool _isPresetTransitioning;
public float Progress
{
get => progress;
set => progress = Mathf.Clamp01(value);
}
public string PresetId => presetId;
public void SetPreset(string id)
{
if (!string.IsNullOrEmpty(id))
{
_previousPresetId = string.IsNullOrEmpty(presetId) ? id : presetId;
presetId = id;
_presetTransitionElapsed = 0f;
_isPresetTransitioning = true;
}
}
private void OnEnable()
{
_previousPresetId = presetId;
_presetTransitionElapsed = presetTransitionDuration;
_isPresetTransitioning = false;
}
void Update()
{
if (config == null || waveRenderer == null) return;
float dt = Time.deltaTime;
if (dt <= 0f)
dt = 1f / 60f;
_elapsedTime += dt;
// CRT startup ease
float startupFactor = Mathf.Clamp01(_elapsedTime / startupDuration);
float startupEase = startupFactor * startupFactor;
// Smooth tracking
_currentValue += (progress - _currentValue) * Mathf.Min(1f, dt * lerpSpeed);
smoothedProgress = _currentValue;
// Interpolate wave params(按当前预设;切换时做一次短暂 crossfade)
var targetParams = config.InterpolatePreset(presetId, _currentValue);
var waveParams = targetParams;
if (_isPresetTransitioning)
{
float duration = Mathf.Max(0.01f, presetTransitionDuration);
_presetTransitionElapsed += dt;
float blend = Smoothstep(Mathf.Clamp01(_presetTransitionElapsed / duration));
var previousParams = config.InterpolatePreset(_previousPresetId, _currentValue);
waveParams = EmotionWaveConfig.WaveParams.Lerp(previousParams, targetParams, blend);
waveParams.Clamp();
if (_presetTransitionElapsed >= duration)
{
_isPresetTransitioning = false;
_previousPresetId = presetId;
}
}
// Inject review parameters based on stage phase
string phase = config.GetStagePhase(_currentValue);
currentStage = phase;
currentStageName = config.GetStageName(_currentValue);
if (phase == "REVIEW")
{
float r0 = 0.35f, r1 = 0.5f, r2 = 0.65f;
if (_currentValue <= r1)
waveParams.reviewIntensity = Smoothstep((_currentValue - r0) / (r1 - r0));
else
waveParams.reviewIntensity = 0.15f + 0.85f * Smoothstep((r2 - _currentValue) / (r2 - r1));
waveParams.featureBoxRate = 3f;
waveParams.thresholdLevel = 0.75f;
waveParams.cursorSpeed = 0.5f;
}
else
{
waveParams.reviewIntensity = 0f;
waveParams.featureBoxRate = 0f;
waveParams.thresholdLevel = 0.8f;
waveParams.cursorSpeed = 0f;
}
// CRT startup fade
waveParams.glowIntensity *= startupEase;
waveParams.lightness *= startupEase;
ApplyPresetSwitchFlash(ref waveParams);
// Push to renderer
waveRenderer.currentParams = waveParams;
waveRenderer.time = _elapsedTime;
}
private void ApplyPresetSwitchFlash(ref EmotionWaveConfig.WaveParams waveParams)
{
float duration = Mathf.Max(0.01f, presetFlashDuration);
if (_presetTransitionElapsed >= duration)
return;
float flash = 1f - Mathf.Clamp01(_presetTransitionElapsed / duration);
flash *= flash;
waveParams.glowIntensity = Mathf.Min(1.2f, waveParams.glowIntensity + presetFlashGlowBoost * flash);
waveParams.lightness = Mathf.Min(80f, waveParams.lightness + presetFlashLightnessBoost * flash);
waveParams.noiseAmount += presetFlashGlitchBoost * flash;
waveParams.jitter += presetFlashGlitchBoost * 0.45f * flash;
waveParams.fragmentation = Mathf.Clamp01(waveParams.fragmentation + 0.2f * flash);
waveParams.noiseSpeed += 6f * flash;
waveParams.lineWidth += 0.35f * flash;
waveParams.Clamp();
}
static float Smoothstep(float t)
{
t = Mathf.Clamp01(t);
return t * t * (3f - 2f * t);
}
}
/// <summary>
/// Makes a field read-only in the Inspector.
/// </summary>
public class ReadOnlyFieldAttribute : PropertyAttribute { }
#if UNITY_EDITOR
[UnityEditor.CustomPropertyDrawer(typeof(ReadOnlyFieldAttribute))]
public class ReadOnlyFieldDrawer : UnityEditor.PropertyDrawer
{
public override void OnGUI(Rect position, UnityEditor.SerializedProperty property, GUIContent label)
{
UnityEditor.EditorGUI.BeginDisabledGroup(true);
UnityEditor.EditorGUI.PropertyField(position, property, label);
UnityEditor.EditorGUI.EndDisabledGroup();
}
}
#endif
}