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"; } /// /// 根据预设 ID 和进度插值波形参数。 /// presetId: normal(默认锚点)/ predicted(红色毛刺)/ modified_joke(尖锐笑话) /// 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; } /// 空状态:保留极弱待机噪波,用于 switch_emotion_wave_config "empty" 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; } /// 内置 predicted 预设:红色、高噪声、毛刺 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; } /// 内置 modified_joke 预设:尖锐、高频、橙红 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; } } }