using System; using System.Linq; using UnityEngine; using Random = UnityEngine.Random; namespace AibisDream.FixSystem { public class WaveformLine : MonoBehaviour { #region 组件索引 private LineRenderer _waveformLine; #endregion private float _screenWidth; private float _screenHeight; #region 状态 private bool _isWavePlaying; #endregion private WaveParam _waveParam; #region Line数据 private float[] _waveformData; private float[] _noiseData; private float[] _spikeData; private float[] _displayData; // 最后展示数据 #endregion [SerializeField] private WaveformConfig config; private void Awake() { InitComponentRefs(); InitValues(); } private void InitValues() { _waveformData = new float[config.samples]; _noiseData = new float[config.samples]; _spikeData = new float[config.samples]; _displayData = new float[config.samples]; _waveformLine.positionCount = config.samples; _waveformLine.enabled = false; } private void InitComponentRefs() { _waveformLine = GetComponent(); } private void Update() { if (_isWavePlaying) { // 绘制线条 DrawWaveform(); } } private void DrawWaveform() { // 计算当前线条的点数据 CalcLinePointsData(); // 计算结束,绘制线条 DrawLine(); } #region 波形数据计算 private void CalcLinePointsData() { // 分组计算: var waveformData = _waveParam.waveformType == WaveformType.Default ? GenerateNoiseData() : GenerateWaveformData(); // 波形数据 // 计算错误数据 var spikeData = GenerateSpikeData(); // 错误尖峰数据 // 合成最终结果 for (int i = 0; i < config.samples; i++) { // 正常线条加错误尖峰 var lineValue = waveformData[i] + spikeData[i]; _displayData[i] = Mathf.Clamp(lineValue, -_screenHeight / 1.8f, _screenHeight / 1.8f); } } private float[] GenerateNoiseData() { for (var i = 0; i < config.samples; i++) { _noiseData[i] = Random.Range(-config.AdjustedNoiseAmplitude, config.AdjustedNoiseAmplitude); } return _noiseData; } private float[] GenerateWaveformData() { // 计算波形 for (int i = 0; i < config.samples; i++) { float basicAngle = TransIndexToAngle(i); // 正波形 float wavePoint = CalcLineValueByAngle(basicAngle); // 如果有相位偏移 if (_waveParam.PhaseShift > 0) { wavePoint -= CalcLineValueByAngle(basicAngle + _waveParam.PhaseShift); } _waveformData[i] = wavePoint; } return _waveformData; } private float TransIndexToAngle(int idx) { return (float)idx / config.samples * Mathf.PI * 2f * config.spatialFrequency; } private float CalcLineValueByAngle(float angle) { // 计算波形值 var value = _waveParam.waveformType switch { WaveformType.Sine => Mathf.Sin(angle), WaveformType.Square => Mathf.Sign(Mathf.Sin(angle)), WaveformType.Triangle => Mathf.PingPong(angle / Mathf.PI, 1f) * 2f - 1f, WaveformType.Sawtooth => angle / (Mathf.PI * 2f) % 1f * 2f - 1f, // 统一周期为 2π WaveformType.Pulse => Mathf.PingPong(angle / (Mathf.PI * 2f), 1f) > 0.5f ? 1f : -1f, // 统一周期为 2π WaveformType.HotNoise => Mathf.PerlinNoise(angle / (Mathf.PI * 2f), Time.time * config.temporalFrequency) * 2f - 1f, // 统一周期为 2π WaveformType.Composite => Mathf.Sin(angle) + Mathf.Sin(angle * 2f) * 0.5f, // 组合波形也统一周期 _ => 0f }; return value * config.amplitude; // 最终返回角度对应波形 } private float[] GenerateSpikeData() { if (CheckErrorShow()) { // 错误点可以显示 for (var i = 0; i < config.samples; i++) { _spikeData[i] = CalcSpikeValueByIdx(i); } } else { // 错误点尚不可显示,全部置为0 Array.Clear(_spikeData, 0, _spikeData.Length); } return _spikeData; } /// /// 错误点是否已经可以定位 /// /// private bool CheckErrorShow() { // 没有错误当然就不显示尖峰点 if (_waveParam.spikeIdx == -1) return false; // 尖峰点小于1/3时显示 return _displayData[_waveParam.spikeIdx] < config.amplitude / 3f; } private float CalcSpikeValueByIdx(int idx) { var distanceToSpike = Mathf.Abs(_waveParam.spikeIdx - idx); // 尖峰范围外直接数据为0 if (distanceToSpike > config.spikeRange) return 0f; // 尖峰范围内计算 // 计算随机范围 var attenuation = (1f - distanceToSpike / 20f) * 0.9f; var baseAmplitude = _displayData[idx] * config.AmplitudeScale * (_screenHeight / 2f); var randomRange = (Mathf.Abs(baseAmplitude) - config.amplitude / 3f) * attenuation; // 得出结果 if (Mathf.Abs(baseAmplitude) < config.amplitude / 3) { return Random.Range(-randomRange, randomRange); // 对 Y 值进行随机扰动 } return 0; } #endregion /// /// 根据计算出的点数绘制线条 /// private void DrawLine() { for (int i = 0; i < config.samples; i++) { // 计算线上每个点的位置 float x = (float)i / (config.samples - 1) * _screenWidth - _screenWidth / 2f; // x轴数据 float y = _displayData[i] * config.AmplitudeScale * (_screenHeight / 2f); Vector3 position = new Vector3(x, y, 0); // 设置 _waveformLine.SetPosition(i, position); } // 设置颜色和宽度 _waveformLine.startColor = config.LineColor; _waveformLine.endColor = config.LineColor; _waveformLine.startWidth = config.LineWidth; _waveformLine.endWidth = config.LineWidth; } private int GeneSpikeIdx() { float[] waveData = GenerateWaveformData(); int resIdx = GetRandomIndexFromTop20Percent(waveData); Array.Clear(waveData, 0, waveData.Length); return resIdx; } int GetRandomIndexFromTop20Percent(float[] array) { int n = array.Length; int topCount = (int)Math.Ceiling(n * 0.2); var indexedArray = array .Select((value, index) => new { Value = Math.Abs(value), Index = index }) .OrderByDescending(item => item.Value) .Take(topCount) .Select(item => item.Index) .ToArray(); int randomIndex = indexedArray[Random.Range(0, indexedArray.Length)]; return randomIndex; } #region 对外暴露 /// /// 设置屏幕尺寸作为参数 /// /// 宽 /// 高 public void SetLineSize(float width, float height) { _screenHeight = height; _screenWidth = width; } /// /// 显示波形 /// public void PlayWave(WaveformType waveformType, float initialPhase, bool hasError) { // 配置波形数据 config.waveformType = waveformType; _waveParam = new WaveParam { waveformType = waveformType, spikeIdx = -1, initPhaseShift = initialPhase, varPhaseShift = 0 }; _waveParam.spikeIdx = hasError ? GeneSpikeIdx() : -1; // 进行播放 _waveformLine.enabled = true; _isWavePlaying = true; } /// /// 停止显示波形 /// public void StopWave() { // 清除数据 config.waveformType = WaveformType.Default; _waveParam = new WaveParam { waveformType = WaveformType.Default, spikeIdx = -1, initPhaseShift = 0, varPhaseShift = 0 }; // 停止 _isWavePlaying = false; _waveformLine.enabled = false; } /// /// 获取尖峰点坐标 /// /// public float GetSpikeX() { if (CheckErrorShow()) { return _waveParam.spikeIdx / (float)config.samples * _screenWidth - _screenWidth / 2; } return float.NegativeInfinity; } /// /// 调整相位 /// /// 相位 public void AdjustPhaseShift(float phaseShift) { _waveParam.varPhaseShift = phaseShift * 2 * Mathf.PI; } #endregion } [Serializable] public class WaveformConfig { public int samples = 1024; public float amplitude = 0.6f; public int spikeRange = 20; public float spatialFrequency = 1f; public float temporalFrequency = 0.1f; public float targetWaveformNoiseAmplitude = 0.1f; public float noiseAmplitude = 0.5f; public Color noiseColor = Color.gray; public Color clearColor = Color.green; public Color hotColor = Color.red; public WaveformType waveformType; public Color LineColor { get { if (waveformType == WaveformType.Default) { return noiseColor; } return waveformType == WaveformType.HotNoise ? hotColor : clearColor; } } public float LineWidth => waveformType != WaveformType.Default ? 0.05f : 0.02f; public float AmplitudeScale => waveformType != WaveformType.Default ? 1f : 0.2f; public float AdjustedNoiseAmplitude => waveformType != WaveformType.Default ? targetWaveformNoiseAmplitude : noiseAmplitude; } public struct WaveParam { public WaveformType waveformType; public float initPhaseShift; public int spikeIdx; public float varPhaseShift; public float PhaseShift => initPhaseShift + varPhaseShift; } }