Files
aibis-dream/Assets/Scripts/MiniGame/HuoShan/EmotionWave/EmotionWaveConfig.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

334 lines
14 KiB
C#

using UnityEngine;
namespace AibisDream.MiniGame.HuoShan.EmotionWave
{
[CreateAssetMenu(fileName = "EmotionWaveConfig", menuName = "AibisDream/EmotionWave Config")]
public class EmotionWaveConfig : ScriptableObject
{
[System.Serializable]
public struct WaveParams
{
[Header("Trajectory Shape")]
[Range(0f, 1f)] public float waveMode;
[Range(0.1f, 1f)] public float timeSpread;
public float omegaX;
public float omegaY;
public float phaseSpeed;
[Min(1)] public int numCycles;
[Header("Amplitude & Waveform")]
public float amplitude;
public float amplitudeY;
[Range(0f, 2f)] public float waveformType;
[Range(0f, 1f)] public float spikiness;
[Range(1f, 12f)] public float waveshapeGain;
public float harmonicStrength;
public float harmonicFreq;
[Header("Noise & Glitch")]
public float noiseAmount;
public float noiseScale;
public float noiseSpeed;
public float jitter;
[Range(0f, 1f)] public float weierstrassAmount;
[Header("Line")]
public float lineWidth;
[Range(0.5f, 0.98f)] public float persistence;
[Range(0f, 1f)] public float fragmentation;
[Header("Color (HSL)")]
public float hue;
[Range(0f, 100f)] public float saturation;
[Range(5f, 80f)] public float lightness;
[Range(0f, 1.2f)] public float glowIntensity;
[Header("Dynamics")]
public float breathRate;
public float breathDepth;
[Header("Review (injected at runtime)")]
[HideInInspector] public float reviewIntensity;
[HideInInspector] public float featureBoxRate;
[HideInInspector] public float thresholdLevel;
[HideInInspector] public float cursorSpeed;
[HideInInspector] public float noiseOnlyBlend;
[Header("Spike")]
[HideInInspector] public float spikeIntensity;
[HideInInspector] public float spikeFreq;
[HideInInspector] public float spikeSharpness;
[HideInInspector] public float envelopeDecay;
[HideInInspector] public float tipLayer;
public static WaveParams Lerp(WaveParams a, WaveParams b, float t)
{
return new WaveParams
{
waveMode = Mathf.Lerp(a.waveMode, b.waveMode, t),
timeSpread = Mathf.Lerp(a.timeSpread, b.timeSpread, t),
omegaX = Mathf.Lerp(a.omegaX, b.omegaX, t),
omegaY = Mathf.Lerp(a.omegaY, b.omegaY, t),
phaseSpeed = Mathf.Lerp(a.phaseSpeed, b.phaseSpeed, t),
numCycles = Mathf.RoundToInt(Mathf.Lerp(a.numCycles, b.numCycles, t)),
amplitude = Mathf.Lerp(a.amplitude, b.amplitude, t),
amplitudeY = Mathf.Lerp(a.amplitudeY, b.amplitudeY, t),
waveformType = Mathf.Lerp(a.waveformType, b.waveformType, t),
spikiness = Mathf.Lerp(a.spikiness, b.spikiness, t),
waveshapeGain = Mathf.Lerp(a.waveshapeGain, b.waveshapeGain, t),
harmonicStrength = Mathf.Lerp(a.harmonicStrength, b.harmonicStrength, t),
harmonicFreq = Mathf.Lerp(a.harmonicFreq, b.harmonicFreq, t),
noiseAmount = Mathf.Lerp(a.noiseAmount, b.noiseAmount, t),
noiseScale = Mathf.Lerp(a.noiseScale, b.noiseScale, t),
noiseSpeed = Mathf.Lerp(a.noiseSpeed, b.noiseSpeed, t),
jitter = Mathf.Lerp(a.jitter, b.jitter, t),
weierstrassAmount = Mathf.Lerp(a.weierstrassAmount, b.weierstrassAmount, t),
lineWidth = Mathf.Lerp(a.lineWidth, b.lineWidth, t),
persistence = Mathf.Lerp(a.persistence, b.persistence, t),
fragmentation = Mathf.Lerp(a.fragmentation, b.fragmentation, t),
hue = Mathf.Lerp(a.hue, b.hue, t),
saturation = Mathf.Lerp(a.saturation, b.saturation, t),
lightness = Mathf.Lerp(a.lightness, b.lightness, t),
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),
};
}
public void Clamp()
{
numCycles = Mathf.Max(2, numCycles);
amplitude = Mathf.Max(0.05f, amplitude);
amplitudeY = Mathf.Max(0.05f, amplitudeY);
lineWidth = Mathf.Max(0.3f, lineWidth);
persistence = Mathf.Clamp(persistence, 0.5f, 0.98f);
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);
waveMode = Mathf.Clamp01(waveMode);
spikiness = Mathf.Clamp01(spikiness);
waveformType = Mathf.Clamp(waveformType, 0f, 2f);
timeSpread = Mathf.Clamp(timeSpread, 0.1f, 1f);
waveshapeGain = Mathf.Clamp(waveshapeGain, 1f, 12f);
weierstrassAmount = Mathf.Clamp01(weierstrassAmount);
}
}
[System.Serializable]
public struct StageAnchor
{
public float position;
public string name;
public string stage;
public WaveParams waveParams;
}
public StageAnchor[] anchors = new StageAnchor[9];
[Header("Presets (normal = default anchors, predicted/modified_joke = optional overrides)")]
[Tooltip("命名预设,用于 switch_emotion_wave_config。id 为 normal/predicted/modified_joke")]
public PresetDefinition[] presets = new PresetDefinition[0];
[System.Serializable]
public struct PresetDefinition
{
public string id;
public StageAnchor[] anchors;
}
static float Smoothstep(float t)
{
t = Mathf.Clamp01(t);
return t * t * (3f - 2f * t);
}
public WaveParams Interpolate(float value)
{
return InterpolatePreset("normal", value);
}
public string GetStageName(float value)
{
value = Mathf.Clamp01(value);
if (anchors == null || anchors.Length == 0) return "";
StageAnchor closest = anchors[0];
float minDist = float.MaxValue;
foreach (var a in anchors)
{
float d = Mathf.Abs(value - a.position);
if (d < minDist) { minDist = d; closest = a; }
}
if (minDist < 0.03f) return closest.name;
StageAnchor lower = anchors[0];
StageAnchor upper = anchors[anchors.Length - 1];
for (int i = 0; i < anchors.Length - 1; i++)
{
if (value >= anchors[i].position && value <= anchors[i + 1].position)
{
lower = anchors[i];
upper = anchors[i + 1];
break;
}
}
float blend = (upper.position - lower.position) > 0.001f
? (value - lower.position) / (upper.position - lower.position)
: 0f;
if (blend < 0.35f) return lower.name;
if (blend > 0.65f) return upper.name;
return $"{lower.name} -> {upper.name}";
}
public string GetStagePhase(float value)
{
if (value < 0.35f) return "BUILD";
if (value < 0.65f) return "REVIEW";
return "ADJUST";
}
/// <summary>
/// 根据预设 ID 和进度插值波形参数。
/// presetId: normal(默认锚点)/ predicted(红色毛刺)/ modified_joke(尖锐笑话)
/// </summary>
public WaveParams InterpolatePreset(string presetId, float value)
{
value = Mathf.Clamp01(value);
StageAnchor[] useAnchors = anchors;
if (!string.IsNullOrEmpty(presetId) && presets != null)
{
foreach (var p in presets)
{
if (p.id == presetId && p.anchors != null && p.anchors.Length > 0)
{
useAnchors = p.anchors;
break;
}
}
}
// 内置预设:empty 空状态,什么都不显示
if (presetId == "empty")
return GetEmptyParams();
// 内置预设:当 config 中未定义时使用程序化默认
if (presetId == "predicted" && (presets == null || !HasPreset("predicted")))
return GetBuiltInPredictedParams(value);
if (presetId == "modified_joke" && (presets == null || !HasPreset("modified_joke")))
return GetBuiltInModifiedJokeParams(value);
return InterpolateAnchors(useAnchors, value);
}
bool HasPreset(string id)
{
if (presets == null) return false;
foreach (var p in presets)
if (p.id == id && p.anchors != null && p.anchors.Length > 0) return true;
return false;
}
static WaveParams InterpolateAnchors(StageAnchor[] anchors, float value)
{
if (anchors == null || anchors.Length == 0) return default;
if (anchors.Length == 1) return anchors[0].waveParams;
float t = Mathf.Clamp01(value);
int idx = 0;
for (int i = 0; i < anchors.Length - 1; i++)
{
if (t <= anchors[i + 1].position) { idx = i; break; }
idx = anchors.Length - 2;
}
float segStart = anchors[idx].position;
float segEnd = anchors[Mathf.Min(anchors.Length - 1, idx + 1)].position;
float segLen = segEnd - segStart;
float tLocal = segLen > 0f ? (t - segStart) / segLen : 0f;
tLocal = tLocal * tLocal * (3f - 2f * tLocal);
var result = WaveParams.Lerp(anchors[idx].waveParams, anchors[Mathf.Min(idx + 1, anchors.Length - 1)].waveParams, tLocal);
result.Clamp();
return result;
}
/// <summary>空状态:保留极弱待机噪波,用于 switch_emotion_wave_config "empty"</summary>
static WaveParams GetEmptyParams()
{
var p = new WaveParams
{
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;
}
/// <summary>内置 predicted 预设:红色、高噪声、毛刺</summary>
static WaveParams GetBuiltInPredictedParams(float value)
{
var baseParams = new WaveParams
{
waveMode = 0.5f, timeSpread = 0.6f, omegaX = 2f, omegaY = 1.5f, phaseSpeed = 1.2f, numCycles = 4,
amplitude = 0.8f, amplitudeY = 0.6f, waveformType = 0.8f, spikiness = 0.95f, waveshapeGain = 8f,
harmonicStrength = 0.4f, harmonicFreq = 2.5f,
noiseAmount = 0.7f, noiseScale = 2.5f, noiseSpeed = 4f, jitter = 0.55f, weierstrassAmount = 0.5f,
lineWidth = 2f, persistence = 0.82f, fragmentation = 0.35f,
hue = 0f, saturation = 85f, lightness = 50f, glowIntensity = 1f,
breathRate = 1.2f, breathDepth = 0.12f
};
baseParams.Clamp();
return baseParams;
}
/// <summary>内置 modified_joke 预设:尖锐、高频、橙红</summary>
static WaveParams GetBuiltInModifiedJokeParams(float value)
{
var baseParams = new WaveParams
{
waveMode = 0.3f, timeSpread = 0.4f, omegaX = 4f, omegaY = 3f, phaseSpeed = 2.5f, numCycles = 6,
amplitude = 1f, amplitudeY = 0.9f, waveformType = 1.8f, spikiness = 0.95f, waveshapeGain = 9f,
harmonicStrength = 0.6f, harmonicFreq = 4f,
noiseAmount = 0.25f, noiseScale = 1.5f, noiseSpeed = 4f, jitter = 0.2f, weierstrassAmount = 0.05f,
lineWidth = 2.2f, persistence = 0.9f, fragmentation = 0.08f,
hue = 25f, saturation = 90f, lightness = 55f, glowIntensity = 1.1f,
breathRate = 2f, breathDepth = 0.08f
};
baseParams.Clamp();
return baseParams;
}
}
}