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