using UnityEngine; namespace AibisDream.MiniGame.HuoShan.EmotionWave { /// /// Controls the EmotionWave visualization. /// Drag the progress slider in the Inspector to drive the 3-act narrative. /// Ported from Web_EmotionWave/js/app.js. /// [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); } } /// /// Makes a field read-only in the Inspector. /// 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 }