empty 改为极弱噪波待机态;增加预设切换 crossfade 与 flash 效果; noiseOnlyBlend 支持纯噪波渲染;两段式归一化使 fillAmount=1 正确填满;switch_emotion_wave_config 支持 progress 参数。 Made-with: Cursor
334 lines
14 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|