diff --git a/Assets/Scripts/MiniGame/HuoShan/EmotionWave/EmotionWaveConfig.cs b/Assets/Scripts/MiniGame/HuoShan/EmotionWave/EmotionWaveConfig.cs index 24d37316d..376c7bf30 100644 --- a/Assets/Scripts/MiniGame/HuoShan/EmotionWave/EmotionWaveConfig.cs +++ b/Assets/Scripts/MiniGame/HuoShan/EmotionWave/EmotionWaveConfig.cs @@ -52,6 +52,7 @@ namespace AibisDream.MiniGame.HuoShan.EmotionWave [HideInInspector] public float featureBoxRate; [HideInInspector] public float thresholdLevel; [HideInInspector] public float cursorSpeed; + [HideInInspector] public float noiseOnlyBlend; [Header("Spike")] [HideInInspector] public float spikeIntensity; @@ -91,6 +92,7 @@ namespace AibisDream.MiniGame.HuoShan.EmotionWave glowIntensity = Mathf.Lerp(a.glowIntensity, b.glowIntensity, t), breathRate = Mathf.Lerp(a.breathRate, b.breathRate, t), breathDepth = Mathf.Lerp(a.breathDepth, b.breathDepth, t), + noiseOnlyBlend = Mathf.Lerp(a.noiseOnlyBlend, b.noiseOnlyBlend, t), }; } @@ -104,6 +106,7 @@ namespace AibisDream.MiniGame.HuoShan.EmotionWave glowIntensity = Mathf.Clamp(glowIntensity, 0f, 1.2f); noiseAmount = Mathf.Max(0f, noiseAmount); fragmentation = Mathf.Clamp01(fragmentation); + noiseOnlyBlend = Mathf.Clamp01(noiseOnlyBlend); hue = ((hue % 360f) + 360f) % 360f; saturation = Mathf.Clamp(saturation, 0f, 100f); lightness = Mathf.Clamp(lightness, 5f, 80f); @@ -255,16 +258,41 @@ namespace AibisDream.MiniGame.HuoShan.EmotionWave return result; } - /// 空状态:什么都不显示,用于 switch_emotion_wave_config "empty" + /// 空状态:保留极弱待机噪波,用于 switch_emotion_wave_config "empty" static WaveParams GetEmptyParams() { var p = new WaveParams { - glowIntensity = 0f, - lineWidth = 0f, - amplitude = 0.01f, - amplitudeY = 0.01f + waveMode = 0f, + timeSpread = 1f, + omegaX = 0f, + omegaY = 0f, + phaseSpeed = 0f, + numCycles = 2, + amplitude = 0.5f, + amplitudeY = 0.5f, + waveformType = 0f, + spikiness = 0f, + waveshapeGain = 1f, + harmonicStrength = 0f, + harmonicFreq = 1f, + noiseAmount = 0.3f, + noiseScale = 5f, + noiseSpeed = 0.8f, + jitter = 0.65f, + weierstrassAmount = 0f, + lineWidth = 0.8f, + persistence = 0.82f, + fragmentation = 0f, + hue = 145f, + saturation = 30f, + lightness = 35f, + glowIntensity = 0.5f, + breathRate = 0.15f, + breathDepth = 0.005f, + noiseOnlyBlend = 1f }; + p.Clamp(); return p; } diff --git a/Assets/Scripts/MiniGame/HuoShan/EmotionWave/EmotionWaveController.cs b/Assets/Scripts/MiniGame/HuoShan/EmotionWave/EmotionWaveController.cs index 55ab9fcec..c27187b6b 100644 --- a/Assets/Scripts/MiniGame/HuoShan/EmotionWave/EmotionWaveController.cs +++ b/Assets/Scripts/MiniGame/HuoShan/EmotionWave/EmotionWaveController.cs @@ -18,6 +18,13 @@ namespace AibisDream.MiniGame.HuoShan.EmotionWave [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; @@ -35,6 +42,9 @@ namespace AibisDream.MiniGame.HuoShan.EmotionWave private float _currentValue; private float _elapsedTime; + private string _previousPresetId = "normal"; + private float _presetTransitionElapsed = 999f; + private bool _isPresetTransitioning; public float Progress { @@ -47,7 +57,19 @@ namespace AibisDream.MiniGame.HuoShan.EmotionWave 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() @@ -55,6 +77,8 @@ namespace AibisDream.MiniGame.HuoShan.EmotionWave if (config == null || waveRenderer == null) return; float dt = Time.deltaTime; + if (dt <= 0f) + dt = 1f / 60f; _elapsedTime += dt; // CRT startup ease @@ -65,8 +89,26 @@ namespace AibisDream.MiniGame.HuoShan.EmotionWave _currentValue += (progress - _currentValue) * Mathf.Min(1f, dt * lerpSpeed); smoothedProgress = _currentValue; - // Interpolate wave params(按当前预设) - var waveParams = config.InterpolatePreset(presetId, _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); @@ -96,11 +138,32 @@ namespace AibisDream.MiniGame.HuoShan.EmotionWave 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); diff --git a/Assets/Scripts/MiniGame/HuoShan/EmotionWave/EmotionWaveManager.cs b/Assets/Scripts/MiniGame/HuoShan/EmotionWave/EmotionWaveManager.cs index a03569067..5602356eb 100644 --- a/Assets/Scripts/MiniGame/HuoShan/EmotionWave/EmotionWaveManager.cs +++ b/Assets/Scripts/MiniGame/HuoShan/EmotionWave/EmotionWaveManager.cs @@ -60,7 +60,7 @@ namespace AibisDream.MiniGame.HuoShan.EmotionWave /// /// 切换波形预设(normal / predicted / modified_joke / empty) - /// empty 为空状态,什么都不显示 + /// empty 为低存在感待机态,会保留极弱噪波 /// public void SetPreset(string presetId) { diff --git a/Assets/Scripts/MiniGame/HuoShan/EmotionWave/EmotionWaveRenderer.cs b/Assets/Scripts/MiniGame/HuoShan/EmotionWave/EmotionWaveRenderer.cs index 5e3134eb6..3a65c72d4 100644 --- a/Assets/Scripts/MiniGame/HuoShan/EmotionWave/EmotionWaveRenderer.cs +++ b/Assets/Scripts/MiniGame/HuoShan/EmotionWave/EmotionWaveRenderer.cs @@ -123,6 +123,23 @@ namespace AibisDream.MiniGame.HuoShan.EmotionWave _points = new WavePoint[numPoints + 1]; } + void GetWaveScreenBounds(out Vector3 center, out Vector3 size) + { + center = waveScreen.position; + size = waveScreen.lossyScale; + + // 对 SpriteRenderer 使用实际世界包围盒,保证 fillAmount 按精灵可见尺寸生效 + if (waveScreen.TryGetComponent(out var spriteRenderer)) + { + Bounds bounds = spriteRenderer.bounds; + if (bounds.size.x > 0.0001f && bounds.size.y > 0.0001f) + { + center = bounds.center; + size = bounds.size; + } + } + } + public override void DrawShapes(Camera cam) { if (waveScreen == null) return; @@ -133,15 +150,9 @@ namespace AibisDream.MiniGame.HuoShan.EmotionWave var p = currentParams; - Vector3 center = waveScreen.position; - Vector3 size = waveScreen.lossyScale; + GetWaveScreenBounds(out Vector3 center, out Vector3 size); float halfW = size.x * 0.5f; float halfH = size.y * 0.5f; - // 归一化:使波形峰值(±amplitude)映射到绘制区边缘,fillAmount=1 时真正填满 - float ampX = Mathf.Max(0.01f, p.amplitude); - float ampY = Mathf.Max(0.01f, p.amplitudeY); - float scaleX = halfW * fillAmount / ampX; - float scaleY = halfH * fillAmount / ampY; float z = center.z; float dt = Time.deltaTime; @@ -183,6 +194,12 @@ namespace AibisDream.MiniGame.HuoShan.EmotionWave float waveformType = p.waveformType; float eased = Smoothstep(waveMode); + float timeDomainWeight = 1f - eased; + + // 低 waveMode 时更像“固定 X + 相位推进”的时域波形,需要更明显的最小相位速度 + float minPhaseSpeed = Mathf.Lerp(0.9f, 0.35f, eased); + float effectivePhaseSpeed = Mathf.Max(p.phaseSpeed, minPhaseSpeed); + float phaseOffset = time * effectivePhaseSpeed * Mathf.Lerp(2.4f, 1f, eased); // --- Generate wave points --- for (int i = 0; i <= numPoints; i++) @@ -192,7 +209,7 @@ namespace AibisDream.MiniGame.HuoShan.EmotionWave // X: sweep + Lissajous oscillation + transition bend float sweepX = (tNorm * 2f - 1f) * p.timeSpread; - float lissPhase = p.omegaX * tParam + time * p.phaseSpeed; + float lissPhase = p.omegaX * tParam + phaseOffset; float blendSpiky = (spikiness > 0.01f) ? Smoothstep(1f - Mathf.Min(1f, waveformType / 0.5f)) : 0f; float spikyX = SpikyWave(lissPhase, spikiness); float periodicX = PeriodicWave(lissPhase, waveformType); @@ -206,8 +223,10 @@ namespace AibisDream.MiniGame.HuoShan.EmotionWave // Y: waveform amplitude float yPhase = p.omegaY * tParam + 0.3f; - float phaseBlend = Smoothstep((0.25f - eased) / 0.08f); - yPhase += time * p.phaseSpeed * phaseBlend; + float timeDomainBlend = Smoothstep((0.25f - eased) / 0.08f); + // XY mode 使用较柔和的相位比;时域模式则增强相位推进,视觉上更像整条波形在流动 + float yPhaseSpeedMul = (0.618f + 0.382f * timeDomainBlend) * Mathf.Lerp(1.8f, 1f, eased); + yPhase += phaseOffset * yPhaseSpeedMul; float spikyY = SpikyWave(yPhase, spikiness); float periodicY = PeriodicWave(yPhase, waveformType); float yRaw = blendSpiky * spikyY + (1f - blendSpiky) * periodicY; @@ -228,13 +247,13 @@ namespace AibisDream.MiniGame.HuoShan.EmotionWave if (p.harmonicStrength > 0.001f) { float hf = p.harmonicFreq; - float hPhaseX = hf * p.omegaX * tParam + time * p.phaseSpeed * 1.7f; - float hPhaseY = hf * p.omegaY * tParam + time * p.phaseSpeed * 0.6f; + float hPhaseX = hf * p.omegaX * tParam + phaseOffset * 1.7f; + float hPhaseY = hf * p.omegaY * tParam + phaseOffset * Mathf.Lerp(1.15f, 0.6f, eased); float hx = p.harmonicStrength * PeriodicWave(hPhaseX, waveformType); float hy = p.harmonicStrength * PeriodicWave(hPhaseY, waveformType); x += hx * eased; y += hy; - if (eased < 0.9f) y += hx * (1f - eased) * 0.5f; + if (eased < 0.9f) y += hx * timeDomainWeight * 0.5f; } // Weierstrass fractal @@ -293,9 +312,30 @@ namespace AibisDream.MiniGame.HuoShan.EmotionWave } } - // Breath - x *= totalBreath; - y *= totalBreath; + float tipIntensity = 0f; + + // empty 待机态:纯横向直线 + Y 方向多层 Perlin 噪波(模拟音频底噪) + float noiseOnlyBlend = p.noiseOnlyBlend; + if (noiseOnlyBlend > 0.001f) + { + float screenEdge = p.amplitude * 1.1f; + float noiseBaseX = Mathf.Lerp(-screenEdge, screenEdge, tNorm); + float noiseTime = time * Mathf.Max(0.1f, p.noiseSpeed); + + float sharpNoise = (Random.value - 0.5f) * 2f; + + float ns = Mathf.Max(0.5f, p.noiseScale); + float envelope = 1f + Noise2D(tNorm * ns * 2f, noiseTime + 47.2f) * p.noiseAmount; + + float noiseX = noiseBaseX; + float noiseY = sharpNoise * p.amplitudeY * p.jitter * envelope; + + noiseY = Mathf.Clamp(noiseY, -p.amplitudeY, p.amplitudeY); + + x = Mathf.Lerp(x, noiseX, noiseOnlyBlend); + y = Mathf.Lerp(y, noiseY, noiseOnlyBlend); + tipIntensity *= 1f - noiseOnlyBlend; + } // Ferrofluid spikes if (spikeIntensity > 0.01f) @@ -312,7 +352,6 @@ namespace AibisDream.MiniGame.HuoShan.EmotionWave } // Tip intensity (red highlight) - float tipIntensity = 0f; if (spikeIntensity > 0.2f) { float spikeRawTip = Mathf.Sin(spikeFreq * tParam + time * 0.5f); @@ -355,7 +394,6 @@ namespace AibisDream.MiniGame.HuoShan.EmotionWave _points[i] = new WavePoint { - worldPos = new Vector3(center.x + x * scaleX, center.y + y * scaleY, z), t = tParam, tNorm = tNorm, tipIntensity = tipIntensity, @@ -365,6 +403,34 @@ namespace AibisDream.MiniGame.HuoShan.EmotionWave }; } + // Two-pass: normalize by actual wave extent so fillAmount=1 truly fills the area + float actualMaxX = 0.001f, actualMaxY = 0.001f; + for (int i = 0; i <= numPoints; i++) + { + float ax = Mathf.Abs(_points[i].rawX); + float ay = Mathf.Abs(_points[i].rawY); + if (ax > actualMaxX) actualMaxX = ax; + if (ay > actualMaxY) actualMaxY = ay; + } + + // In noise-only mode, blend toward base amplitude to prevent tiny noise from being amplified to full screen + float baseMaxX = Mathf.Max(0.01f, p.amplitude); + float baseMaxY = Mathf.Max(0.01f, p.amplitudeY); + float nBlend = p.noiseOnlyBlend; + float normX = Mathf.Lerp(actualMaxX, baseMaxX, nBlend); + float normY = Mathf.Lerp(actualMaxY, baseMaxY, nBlend); + + float scaleX = halfW * fillAmount / normX; + float scaleY = halfH * fillAmount / normY; + + for (int i = 0; i <= numPoints; i++) + { + _points[i].worldPos = new Vector3( + center.x + _points[i].rawX * totalBreath * scaleX, + center.y + _points[i].rawY * totalBreath * scaleY, + z); + } + // --- Draw --- using (Draw.Command(cam)) { @@ -465,7 +531,7 @@ namespace AibisDream.MiniGame.HuoShan.EmotionWave // --- Beam spot --- Draw.BlendMode = ShapesBlendMode.Additive; - float beamSpeed = p.phaseSpeed * 8f; + float beamSpeed = effectivePhaseSpeed * 8f; float beamPhase = (time * beamSpeed) % totalT; int beamIdx = Mathf.FloorToInt((beamPhase / totalT) * numPoints); if (beamIdx >= 0 && beamIdx < _points.Length) diff --git a/Assets/Scripts/MiniGame/HuoShan/EmotionWave/EmotionWaveYarnCommand.cs b/Assets/Scripts/MiniGame/HuoShan/EmotionWave/EmotionWaveYarnCommand.cs index 318d7152d..eb2404ada 100644 --- a/Assets/Scripts/MiniGame/HuoShan/EmotionWave/EmotionWaveYarnCommand.cs +++ b/Assets/Scripts/MiniGame/HuoShan/EmotionWave/EmotionWaveYarnCommand.cs @@ -128,17 +128,22 @@ namespace AibisDream.MiniGame.HuoShan.EmotionWave /// /// 切换波形预设(normal / predicted / modified_joke / empty) - /// empty 为空状态,什么都不显示 + /// empty 为低存在感待机态,会保留极弱噪波 /// 用法: <> /// <> /// <> + /// <> /// [YarnCommand("switch_emotion_wave_config")] - public static void SwitchEmotionWaveConfig(string presetId) + public static void SwitchEmotionWaveConfig(string presetId, float progress = -1f) { var manager = GetManager(); if (manager != null) + { manager.SetPreset(presetId); + if (progress >= 0f) + manager.SetProgress(progress); + } } #endregion