using UnityEngine; [ExecuteInEditMode] [RequireComponent(typeof(LineRenderer))] public class SynthWaveform : MonoBehaviour { [System.Serializable] public class SynthVoice { public bool enabled = true; // 是否启用该波形 [Range(0f, 1f)] public float waveMorph = 0f; // 0~1 控制波形从三角到脉冲环形渐变 public float frequency = 1f; // 基础频率 public int octave = 0; // 八度调整(+/-) [Range(0f, 1f)] public float amplitude = 1f; // 振幅 [Range(0.1f, 0.9f)] public float pulseWidth = 0.5f; // 脉冲宽度,仅在Pulse阶段有效 } public SynthVoice[] voices = new SynthVoice[3]; [Header("绘图设置")] public int resolution = 300; public float waveWidth = 10f; public float waveHeight = 1f; public float speed = 2f; [Header("周期设置")] [Range(1, 20)] public int cycles = 4; // 在waveWidth内显示几个波周期 [Header("跳动点")] public Transform glowPointInstance; // 小球跳动点物体 private LineRenderer lineRenderer; private Vector3[] points; private float phaseOffset = 0f; void OnEnable() { lineRenderer = GetComponent(); lineRenderer.positionCount = resolution; points = new Vector3[resolution]; } void Update() { phaseOffset += Time.deltaTime * speed; UpdateWave(); UpdateGlowPoint(); } void UpdateWave() { Vector3 origin = transform.position; Quaternion rotation = transform.rotation; for (int i = 0; i < resolution; i++) { float x = ((float)i / (resolution - 1)) * waveWidth; float time = (x / waveWidth) * cycles + phaseOffset; float sample = 0f; foreach (var voice in voices) { if (!voice.enabled) continue; float freq = voice.frequency * Mathf.Pow(2, voice.octave); float wavePos = (time * freq) % 1f; float morphValue = voice.waveMorph * 20f; // 0~20两圈循环 float val = MorphWaveCircular(morphValue, wavePos, voice.pulseWidth); sample += val * voice.amplitude; } sample = Mathf.Clamp(sample, -1f, 1f); Vector3 localPos = new Vector3(x, sample * waveHeight, 0f); points[i] = origin + rotation * localPos; } lineRenderer.SetPositions(points); } void UpdateGlowPoint() { if (glowPointInstance == null || points == null || points.Length == 0) return; // phaseOffset在0~1之间循环,映射points索引 float t = phaseOffset % 1f; int idx = Mathf.RoundToInt(t * (resolution - 1)); idx = Mathf.Clamp(idx, 0, resolution - 1); // 小球X固定为最右端点X float fixedX = points[resolution - 1].x; // 小球Y跟随索引对应的波形Y float y = points[idx].y; glowPointInstance.position = new Vector3(fixedX, y, points[idx].z); } // 环形渐变波形 morphValue 0~20,循环两圈 float MorphWaveCircular(float morphValue, float time, float pulseWidth) { float val = morphValue % 20f; float[] keyPoints = { 0f, 4f, 7f, 10f, 14f, 17f, 20f }; int[] waveIDs = { 0, 1, 2, 3, 3, 0, 1 }; int idx = 0; for (int i = 0; i < keyPoints.Length - 1; i++) { if (val >= keyPoints[i] && val < keyPoints[i + 1]) { idx = i; break; } } float t = (val - keyPoints[idx]) / (keyPoints[idx + 1] - keyPoints[idx]); float waveA = GetWaveByID(waveIDs[idx], time, pulseWidth); float waveB = GetWaveByID(waveIDs[idx + 1], time, pulseWidth); return Mathf.Lerp(waveA, waveB, t); } float GetWaveByID(int id, float t, float pulseWidth) { switch (id) { case 0: return Triangle(t); case 1: return Sawtooth(t); case 2: return Square(t); case 3: return Pulse(t, pulseWidth); default: return 0f; } } float Sawtooth(float t) { return 2f * t - 1f; } float Triangle(float t) { return 1f - 4f * Mathf.Abs(Mathf.Round(t - 0.25f) - (t - 0.25f)); } float Square(float t) { return t < 0.5f ? 1f : -1f; } float Pulse(float t, float pulseWidth) { return t < pulseWidth ? 1f : -1f; } }