Files
2026-01-22 19:58:35 +08:00

1030 lines
35 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;
using AibisDream.FixSystem;
namespace AibisDream.MiniGame.HuoShan
{
/// <summary>
/// 波形检测器 - 用于显示和分析波形
///
/// 功能:
/// - 显示各种波形(情绪、逻辑、笑话等)
/// - 扫描模式:拖动进度条寻找清晰波形
/// - 波形效果:检测闪烁、变换动画等
/// </summary>
public class WaveformDetector : MonoBehaviour
{
#region 序列化字段
[Header("设备配置")]
[SerializeField] private WaveformConfig waveformConfig;
[SerializeField] private Transform deviceContainer;
[Header("波形渲染器")]
[SerializeField] private WaveformRenderer primaryWaveform;
[Header("动画设置")]
[SerializeField] private Vector2 hiddenPosition = new Vector2(800f, 0f);
[SerializeField] private Vector2 shownPosition = new Vector2(0f, 0f);
[SerializeField] private float slideInDuration = 0.5f;
[SerializeField] private float slideOutDuration = 0.3f;
// 复古机械感:避免OutBack/InBack这种“弹簧感”过强的曲线
[SerializeField] private Ease slideInEase = Ease.OutCubic;
[SerializeField] private Ease slideOutEase = Ease.InCubic;
[Header("扫描UI")]
[SerializeField] private GameObject scannerPanel;
[SerializeField] private Slider scanSlider;
[SerializeField] private RawImage visitedAreaImage;
[SerializeField] private Text scanStatusText;
[SerializeField] private Button lockButton;
[SerializeField] private Text lockButtonText;
[Header("扫描设置")]
[SerializeField] private float clearPointPosition = 0.35f;
[SerializeField] private float clearThreshold = 0.15f;
[SerializeField] private float visitedCoverageRequired = 0.9f;
[SerializeField] private float notFoundDelay = 1f;
[Header("扫描表现")]
[SerializeField] private float scanBlurClarity = 0.06f;
[SerializeField] private float scanBlurNoise = 0.75f;
[SerializeField] private float scanClearNoise = 0.08f;
[SerializeField] private float scanClarityWidth = 0.06f;
[SerializeField] private float scanClarityRangeMultiplier = 2.0f;
[SerializeField] private float scanClarityPower = 2.2f;
[SerializeField] private float scanLogicSwitchClarity = 0.7f;
[SerializeField] private Vector2 scanFilterLogicWeightRange = new Vector2(0.35f, 0.6f);
[SerializeField] private Vector2 scanFilterJokeWeightRange = new Vector2(0.7f, 0.3f);
[Header("结果UI")]
[SerializeField] private GameObject resultPanel;
[SerializeField] private Text resultText;
[Header("连接状态")]
[SerializeField] private Text connectionStatusText;
[SerializeField] private Image connectionIndicator;
[Header("复古机械风格(UI/提示色)")]
[SerializeField] private bool useMechanicalUiStyle = true;
[SerializeField] private Color uiOnlineColor = new Color(0.30f, 0.90f, 0.55f, 1f); // CRT绿
[SerializeField] private Color uiOfflineColor = new Color(0.40f, 0.40f, 0.40f, 1f);
[SerializeField] private Color uiWarnColor = new Color(1.00f, 0.70f, 0.20f, 1f); // 仪表琥珀
[SerializeField] private Color uiErrorColor = new Color(0.95f, 0.30f, 0.25f, 1f);
[SerializeField] private Color visitedColor = new Color(0.30f, 0.90f, 0.55f, 0.65f);
[SerializeField] private Color unvisitedColor = new Color(0.18f, 0.18f, 0.18f, 0.35f);
[SerializeField] private Color sweepScanLineColor = new Color(1.00f, 0.70f, 0.20f, 0.85f);
[Header("复古机械风格(文案)")]
[SerializeField] private string statusPrefix = "状态:";
[SerializeField] private string scanCalibratingText = "校准中:调节游标,寻找稳定窗口…";
[SerializeField] private string scanSearchingText = "扫描中:寻找稳定窗口…";
[SerializeField] private string scanFineTuneText = "信号趋稳:继续微调…";
[SerializeField] private string scanReadyToLockText = "信号稳定:允许锁定";
[SerializeField] private string scanCompletedNotFoundText = "扫描完成:未检出匹配波形";
[SerializeField] private string emotionNotFoundText = "扫描完成:输出端未检出原始情绪波形";
[Header("音效")]
[SerializeField] private AudioClip scanSound;
[SerializeField] private AudioClip lockSound;
[SerializeField] private AudioClip notFoundSound;
[SerializeField] private AudioClip connectSound;
[SerializeField] private AudioClip transformSound;
#endregion
#region 私有字段
private bool _isShown = false;
private AudioSource _audioSource;
// 扫描模式
private bool _isScanMode = false;
private ScanMode _currentScanMode = ScanMode.None;
private bool _isLocked = false;
private float _lockedPosition = 0f;
private string _lockedTimestamp = "";
// 已访问区域追踪
private bool[] _visitedSegments;
private int _segmentCount = 100;
private float _visitedCoverage = 0f;
private bool _fullyCovered = false;
private float _notFoundTimer = 0f;
private bool _notFoundTriggered = false;
private Texture2D _visitedTexture;
// 当前连接的模块
private string _connectedModuleId = "";
#endregion
#region 枚举
public enum ScanMode
{
None,
Logic,
Emotion
}
#endregion
#region 属性
public bool IsShown => _isShown;
public bool IsScanMode => _isScanMode;
public bool IsLocked => _isLocked;
public bool FullyCovered => _fullyCovered;
public bool NotFoundTriggered => _notFoundTriggered;
public string LockedTimestamp => _lockedTimestamp;
public string ConnectedModuleId => _connectedModuleId;
#endregion
#region 生命周期
private void Awake()
{
_audioSource = GetComponent<AudioSource>();
if (_audioSource == null)
{
_audioSource = gameObject.AddComponent<AudioSource>();
}
if (deviceContainer != null)
{
deviceContainer.localPosition = hiddenPosition;
}
HideAllPanels();
_visitedSegments = new bool[_segmentCount];
CreateVisitedTexture();
if (scanSlider != null)
{
scanSlider.onValueChanged.AddListener(OnScanSliderChanged);
}
if (lockButton != null)
{
lockButton.onClick.AddListener(OnLockButtonClicked);
}
FixSystemCenter.SystemDic.Register(this);
}
private void OnDestroy()
{
if (scanSlider != null)
{
scanSlider.onValueChanged.RemoveListener(OnScanSliderChanged);
}
if (lockButton != null)
{
lockButton.onClick.RemoveListener(OnLockButtonClicked);
}
if (_visitedTexture != null)
{
Destroy(_visitedTexture);
}
FixSystemCenter.SystemDic.Unregister<WaveformDetector>();
}
private void Update()
{
if (_isScanMode && _currentScanMode == ScanMode.Emotion && _fullyCovered && !_notFoundTriggered)
{
_notFoundTimer += Time.deltaTime;
if (_notFoundTimer >= notFoundDelay)
{
TriggerNotFound();
}
}
}
private void HideAllPanels()
{
if (scannerPanel != null) scannerPanel.SetActive(false);
if (resultPanel != null) resultPanel.SetActive(false);
}
#endregion
#region 设备显示/隐藏
public IEnumerator Show(float duration = -1f)
{
if (_isShown) yield break;
float actualDuration = duration > 0 ? duration : slideInDuration;
_isShown = true;
if (deviceContainer != null)
{
// 这里使用localPosition初始化,因此也使用DOLocalMove,避免世界坐标导致的“漂移感”
yield return deviceContainer.DOMove(shownPosition, actualDuration)
.SetEase(slideInEase)
.WaitForCompletion();
}
}
public IEnumerator Hide(float duration = -1f)
{
if (!_isShown) yield break;
float actualDuration = duration > 0 ? duration : slideOutDuration;
_isShown = false;
StopScanMode();
if (deviceContainer != null)
{
yield return deviceContainer.DOMove(hiddenPosition, actualDuration)
.SetEase(slideOutEase)
.WaitForCompletion();
}
if (primaryWaveform != null) primaryWaveform.SetVisible(false);
HideAllPanels();
}
#endregion
#region 模块连接
public IEnumerator ConnectToModule(string moduleId)
{
if (!_isShown)
{
yield return Show();
}
_connectedModuleId = moduleId;
PlaySound(connectSound);
if (connectionStatusText != null)
{
connectionStatusText.text = useMechanicalUiStyle
? $"LINK: ONLINE / {moduleId}"
: $"连接状态: 已联机 / {moduleId}";
}
if (connectionIndicator != null)
{
connectionIndicator.color = useMechanicalUiStyle ? uiOnlineColor : new Color(0.35f, 0.85f, 0.55f);
}
if (primaryWaveform != null)
{
var settings = waveformConfig.GetWaveformById("filter_output");
primaryWaveform.ApplySettings(settings);
primaryWaveform.SetNoiseAmount(0.8f);
primaryWaveform.SetClarity(0f);
primaryWaveform.SetVisible(true);
}
yield return new WaitForSeconds(0.3f);
}
public void Disconnect()
{
_connectedModuleId = "";
if (connectionStatusText != null)
{
connectionStatusText.text = useMechanicalUiStyle ? "LINK: OFFLINE" : "连接状态: 断开";
}
if (connectionIndicator != null)
{
connectionIndicator.color = useMechanicalUiStyle ? uiOfflineColor : new Color(0.45f, 0.45f, 0.45f);
}
StopScanMode();
}
#endregion
#region 波形显示
public IEnumerator ShowWaveform(string waveformId, float duration = 0.5f)
{
if (waveformConfig == null)
{
Debug.LogError("WaveformDetector: 波形配置未设置!");
yield break;
}
if (!_isShown)
{
yield return Show();
}
var settings = waveformConfig.GetWaveformById(waveformId);
if (primaryWaveform != null)
{
primaryWaveform.ApplySettings(settings);
primaryWaveform.SetClarity(1f);
primaryWaveform.SetNoiseAmount(0f);
primaryWaveform.SetVisible(true);
primaryWaveform.SetAlpha(0f);
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
primaryWaveform.SetAlpha(elapsed / duration);
yield return null;
}
primaryWaveform.SetAlpha(1f);
}
}
public IEnumerator HideWaveform(float duration = 0.3f)
{
if (primaryWaveform != null && primaryWaveform.gameObject.activeInHierarchy)
{
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
primaryWaveform.SetAlpha(1f - elapsed / duration);
yield return null;
}
primaryWaveform.SetVisible(false);
}
}
#endregion
#region 扫描模式
public IEnumerator EnableScanMode(ScanMode mode, string label = "扫描波形")
{
if (!_isShown)
{
yield return Show();
}
_isScanMode = true;
_currentScanMode = mode;
_isLocked = false;
_notFoundTriggered = false;
_notFoundTimer = 0f;
ResetVisitedArea();
if (mode == ScanMode.Logic)
{
clearPointPosition = Random.Range(0.25f, 0.45f);
}
else
{
clearPointPosition = -1f;
}
if (primaryWaveform != null && string.IsNullOrEmpty(_connectedModuleId))
{
yield return ConnectToModule("滤波器");
}
if (scannerPanel != null)
{
scannerPanel.SetActive(true);
}
if (scanSlider != null)
{
scanSlider.value = 0.5f;
scanSlider.interactable = true;
}
if (lockButton != null)
{
lockButton.interactable = false;
if (lockButtonText != null)
{
lockButtonText.text = "锁定";
}
}
if (scanStatusText != null)
{
scanStatusText.text = useMechanicalUiStyle
? $"{scanCalibratingText}{label}"
: $"校准中:移动游标{label}...";
}
UpdateWaveformByPosition(0.5f);
}
private void OnScanSliderChanged(float value)
{
if (!_isScanMode || _isLocked) return;
MarkVisited(value);
UpdateVisitedAreaDisplay();
UpdateWaveformByPosition(value);
bool isClear = IsNearClearPoint(value);
if (lockButton != null)
{
lockButton.interactable = isClear && _currentScanMode == ScanMode.Logic;
}
UpdateScanStatus(value, isClear);
}
private void UpdateWaveformByPosition(float position)
{
if (primaryWaveform == null) return;
if (_currentScanMode == ScanMode.Logic)
{
float distanceFromClear = Mathf.Abs(position - clearPointPosition);
float maxDistance = clearThreshold * scanClarityRangeMultiplier;
float t = 1f - Mathf.Clamp01(distanceFromClear / maxDistance);
float shaped = Mathf.Pow(t, scanClarityPower);
float clarity = Mathf.Lerp(scanBlurClarity, 1f, shaped);
float noiseAmount = Mathf.Lerp(scanBlurNoise, scanClearNoise, shaped);
primaryWaveform.SetClarityPosition(position);
primaryWaveform.SetClarityWidth(scanClarityWidth);
primaryWaveform.SetClarity(clarity);
primaryWaveform.SetNoiseAmount(noiseAmount);
if (clarity >= scanLogicSwitchClarity)
{
primaryWaveform.SetWaveType(WaveformType.Logic);
}
else
{
primaryWaveform.SetWaveType(WaveformType.FilterOutput);
float logicWeight = Mathf.Lerp(scanFilterLogicWeightRange.x, scanFilterLogicWeightRange.y, shaped);
float jokeWeight = Mathf.Lerp(scanFilterJokeWeightRange.x, scanFilterJokeWeightRange.y, shaped);
primaryWaveform.SetSynthesisWeights(logicWeight, jokeWeight);
}
}
else
{
primaryWaveform.SetClarityPosition(position);
primaryWaveform.SetClarity(0f);
primaryWaveform.SetNoiseAmount(0.8f);
}
}
private bool IsNearClearPoint(float position)
{
if (_currentScanMode != ScanMode.Logic) return false;
return Mathf.Abs(position - clearPointPosition) < clearThreshold * 0.5f;
}
private void UpdateScanStatus(float position, bool isClear)
{
if (scanStatusText == null) return;
if (_currentScanMode == ScanMode.Logic)
{
if (isClear)
{
scanStatusText.text = useMechanicalUiStyle
? $"{statusPrefix}{scanReadyToLockText}"
: "✓ 信号稳定,可执行锁定";
}
else
{
float distanceFromClear = Mathf.Abs(position - clearPointPosition);
if (distanceFromClear < clearThreshold)
{
scanStatusText.text = useMechanicalUiStyle
? $"{statusPrefix}{scanFineTuneText}"
: "信号趋稳中...继续微调";
}
else
{
scanStatusText.text = useMechanicalUiStyle
? $"{statusPrefix}{scanSearchingText}"
: "扫描中:寻找稳定窗口...";
}
}
}
else
{
if (_fullyCovered)
{
scanStatusText.text = useMechanicalUiStyle
? $"{statusPrefix}{scanCompletedNotFoundText}"
: "⚠ 扫描完成:未命中匹配波形";
}
else
{
scanStatusText.text = useMechanicalUiStyle
? $"{statusPrefix}扫描进度:{_visitedCoverage * 100:F0}%"
: $"扫描进度:{_visitedCoverage * 100:F0}%";
}
}
}
private void OnLockButtonClicked()
{
if (!_isScanMode || _isLocked) return;
if (_currentScanMode != ScanMode.Logic) return;
float currentPosition = scanSlider != null ? scanSlider.value : 0.5f;
if (!IsNearClearPoint(currentPosition)) return;
_isLocked = true;
_lockedPosition = currentPosition;
_lockedTimestamp = GenerateTimestamp();
PlaySound(lockSound);
if (lockButton != null)
{
lockButton.interactable = false;
if (lockButtonText != null)
{
lockButtonText.text = "✓ 已锁定";
}
}
if (scanSlider != null)
{
scanSlider.interactable = false;
}
if (scanStatusText != null)
{
scanStatusText.text = useMechanicalUiStyle
? $"{statusPrefix}锁定完成 | 时间戳: {_lockedTimestamp}"
: $"✓ 锁定完成 | 时间戳: {_lockedTimestamp}";
}
if (primaryWaveform != null)
{
primaryWaveform.SetClarity(1f);
primaryWaveform.SetNoiseAmount(0f);
primaryWaveform.SetWaveType(WaveformType.Logic);
}
Debug.Log($"WaveformDetector: 锁定成功,时间戳: {_lockedTimestamp}");
}
private void TriggerNotFound()
{
_notFoundTriggered = true;
PlaySound(notFoundSound);
if (scanStatusText != null)
{
scanStatusText.text = useMechanicalUiStyle
? $"{statusPrefix}{emotionNotFoundText}"
: "⚠ 扫描完成:未检出原始情绪波形";
}
if (scanSlider != null)
{
scanSlider.interactable = false;
}
Debug.Log("WaveformDetector: 情绪波形未找到");
}
public void StopScanMode()
{
_isScanMode = false;
_currentScanMode = ScanMode.None;
_isLocked = false;
if (scannerPanel != null)
{
scannerPanel.SetActive(false);
}
}
#endregion
#region 已访问区域追踪
private void CreateVisitedTexture()
{
_visitedTexture = new Texture2D(_segmentCount, 1, TextureFormat.RGBA32, false);
_visitedTexture.filterMode = FilterMode.Point;
_visitedTexture.wrapMode = TextureWrapMode.Clamp;
if (visitedAreaImage != null)
{
visitedAreaImage.texture = _visitedTexture;
}
ResetVisitedArea();
}
private void ResetVisitedArea()
{
for (int i = 0; i < _segmentCount; i++)
{
_visitedSegments[i] = false;
}
_visitedCoverage = 0f;
_fullyCovered = false;
UpdateVisitedAreaDisplay();
}
private void MarkVisited(float position)
{
int segmentIndex = Mathf.Clamp((int)(position * _segmentCount), 0, _segmentCount - 1);
int range = 3;
for (int i = segmentIndex - range; i <= segmentIndex + range; i++)
{
if (i >= 0 && i < _segmentCount)
{
_visitedSegments[i] = true;
}
}
int visitedCount = 0;
for (int i = 0; i < _segmentCount; i++)
{
if (_visitedSegments[i]) visitedCount++;
}
_visitedCoverage = (float)visitedCount / _segmentCount;
_fullyCovered = _visitedCoverage >= visitedCoverageRequired;
}
private void UpdateVisitedAreaDisplay()
{
if (_visitedTexture == null || visitedAreaImage == null) return;
for (int i = 0; i < _segmentCount; i++)
{
// 使用序列化颜色,便于整体风格统一(CRT绿/暗底)
_visitedTexture.SetPixel(i, 0, _visitedSegments[i] ? visitedColor : unvisitedColor);
}
_visitedTexture.Apply();
}
#endregion
#region 时间戳功能
private string GenerateTimestamp()
{
return $"07:12:{Random.Range(2, 10):D2}";
}
public IEnumerator ShowTimestampWaveform()
{
if (string.IsNullOrEmpty(_lockedTimestamp))
{
Debug.LogWarning("WaveformDetector: 没有锁定的时间戳");
yield break;
}
StopScanMode();
if (primaryWaveform != null)
{
var settings = waveformConfig.GetWaveformById("emotion");
primaryWaveform.ApplySettings(settings);
primaryWaveform.SetClarity(1f);
primaryWaveform.SetNoiseAmount(0f);
primaryWaveform.SetVisible(true);
}
ShowResult(true, $"时间戳 [{_lockedTimestamp}] 的原始波形\n类型:情绪波形(悲伤/压抑)\n⚠ 注意:波形与当前输出不一致");
yield return new WaitForSeconds(0.5f);
}
#endregion
#region 结果显示
public void ShowResult(bool isSuccess, string message)
{
if (resultPanel != null) resultPanel.SetActive(true);
if (resultText != null)
{
resultText.text = message;
if (useMechanicalUiStyle)
{
resultText.color = isSuccess ? uiOnlineColor : uiErrorColor;
}
else
{
resultText.color = isSuccess ? new Color(0.65f, 0.95f, 0.75f) : new Color(0.95f, 0.45f, 0.45f);
}
}
}
public void HideResult()
{
if (resultPanel != null) resultPanel.SetActive(false);
}
#endregion
#region 波形变换
public IEnumerator TransformWaveform(string fromWaveformId, string toWaveformId, float duration = 1f)
{
if (waveformConfig == null || primaryWaveform == null) yield break;
var fromSettings = waveformConfig.GetWaveformById(fromWaveformId);
var toSettings = waveformConfig.GetWaveformById(toWaveformId);
if (!_isShown) yield return Show();
primaryWaveform.ApplySettings(fromSettings);
primaryWaveform.SetClarity(1f);
primaryWaveform.SetNoiseAmount(0f);
primaryWaveform.SetVisible(true);
PlaySound(transformSound);
primaryWaveform.StartTransform(fromSettings.waveType, toSettings.waveType);
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float progress = elapsed / duration;
primaryWaveform.UpdateTransformProgress(progress);
Color currentPrimary = Color.Lerp(fromSettings.primaryColor, toSettings.primaryColor, progress);
Color currentSecondary = Color.Lerp(fromSettings.secondaryColor, toSettings.secondaryColor, progress);
primaryWaveform.SetColors(currentPrimary, currentSecondary);
// 用透明度闪烁代替位置抖动
if (progress > 0.3f && progress < 0.7f)
{
float intensity = 1 - Mathf.Abs(progress - 0.5f) * 2f;
float flicker = 0.7f + Mathf.Sin(progress * Mathf.PI * 20f) * intensity * 0.3f;
primaryWaveform.SetAlpha(flicker);
}
else
{
primaryWaveform.SetAlpha(1f);
}
yield return null;
}
primaryWaveform.FinishTransform();
primaryWaveform.ApplySettings(toSettings);
primaryWaveform.SetAlpha(1f);
}
#endregion
#region 波形效果
private struct WaveformVisualState
{
public Color PrimaryColor;
public Color SecondaryColor;
public float Alpha;
public float Clarity;
public float ClarityPosition;
public float ClarityWidth;
public float NoiseAmount;
}
private WaveformVisualState CaptureWaveformState()
{
return new WaveformVisualState
{
PrimaryColor = primaryWaveform.PrimaryColor,
SecondaryColor = primaryWaveform.SecondaryColor,
Alpha = primaryWaveform.CurrentAlpha,
Clarity = primaryWaveform.Clarity,
ClarityPosition = primaryWaveform.ClarityPosition,
ClarityWidth = primaryWaveform.ClarityWidth,
NoiseAmount = primaryWaveform.NoiseAmount
};
}
private void RestoreWaveformState(WaveformVisualState state)
{
primaryWaveform.SetColors(state.PrimaryColor, state.SecondaryColor);
primaryWaveform.SetClarity(state.Clarity);
primaryWaveform.SetClarityPosition(state.ClarityPosition);
primaryWaveform.SetClarityWidth(state.ClarityWidth);
primaryWaveform.SetNoiseAmount(state.NoiseAmount);
primaryWaveform.SetAlpha(state.Alpha);
}
/// <summary>
/// 波形检测效果(闪烁)
/// </summary>
public IEnumerator WaveformCheckEffect(int blinkCount = 3, float blinkInterval = 0.15f)
{
if (primaryWaveform == null) yield break;
WaveformVisualState originalState = CaptureWaveformState();
primaryWaveform.SetClarity(Mathf.Max(originalState.Clarity, 0.4f));
primaryWaveform.SetClarityWidth(Mathf.Max(originalState.ClarityWidth, 0.06f));
primaryWaveform.SetNoiseAmount(Mathf.Max(originalState.NoiseAmount, 0.25f));
for (int i = 0; i < blinkCount; i++)
{
yield return SweepAnalysis(blinkInterval, 0.05f, 0.95f);
primaryWaveform.SetAlpha(0.6f);
yield return new WaitForSeconds(blinkInterval * 0.3f);
primaryWaveform.SetAlpha(originalState.Alpha);
}
RestoreWaveformState(originalState);
}
/// <summary>
/// 屏蔽词检测效果(扫描线从左到右扫过)
/// </summary>
public IEnumerator WaveformSweepCheck(int sweepCount = 2, float sweepDuration = 0.25f)
{
if (primaryWaveform == null) yield break;
// 使用扫描线进行检测,不改变波形颜色
primaryWaveform.SetScanLineColor(useMechanicalUiStyle ? sweepScanLineColor : new Color(0.2f, 0.75f, 0.85f, 0.85f));
for (int i = 0; i < sweepCount; i++)
{
yield return SweepWithScanLine(sweepDuration, 0.02f, 0.98f);
yield return new WaitForSeconds(0.05f);
}
primaryWaveform.HideScanLine();
}
/// <summary>
/// 限幅检测效果(显示上下限线并闪烁)
/// </summary>
public IEnumerator WaveformRangeCheck(int pulseCount = 2, float pulseInterval = 0.12f)
{
if (primaryWaveform == null) yield break;
// 显示上下限线
primaryWaveform.ShowLimitLines();
for (int i = 0; i < pulseCount; i++)
{
// 闪烁效果
yield return LimitLinesPulse(pulseInterval);
yield return new WaitForSeconds(pulseInterval * 0.3f);
}
primaryWaveform.HideLimitLines();
}
private IEnumerator SweepAnalysis(float duration, float start, float end)
{
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = Mathf.Clamp01(elapsed / duration);
primaryWaveform.SetClarityPosition(Mathf.Lerp(start, end, t));
yield return null;
}
}
/// <summary>
/// 使用扫描线进行横向扫描
/// </summary>
private IEnumerator SweepWithScanLine(float duration, float start, float end)
{
primaryWaveform.ShowScanLine(start);
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = Mathf.Clamp01(elapsed / duration);
float position = Mathf.Lerp(start, end, t);
primaryWaveform.SetScanLinePosition(position);
yield return null;
}
}
/// <summary>
/// 限幅线闪烁效果
/// </summary>
private IEnumerator LimitLinesPulse(float duration)
{
float halfDuration = duration * 0.5f;
float elapsed = 0f;
// 淡入
while (elapsed < halfDuration)
{
elapsed += Time.deltaTime;
float alpha = Mathf.Lerp(0.3f, 1f, elapsed / halfDuration);
primaryWaveform.SetLimitLinesAlpha(alpha);
yield return null;
}
// 淡出
elapsed = 0f;
while (elapsed < halfDuration)
{
elapsed += Time.deltaTime;
float alpha = Mathf.Lerp(1f, 0.3f, elapsed / halfDuration);
primaryWaveform.SetLimitLinesAlpha(alpha);
yield return null;
}
}
private IEnumerator ProbeAt(float position, float duration)
{
primaryWaveform.SetClarityPosition(position);
primaryWaveform.SetAlpha(0.6f);
yield return new WaitForSeconds(duration);
primaryWaveform.SetAlpha(1f);
yield return new WaitForSeconds(duration * 0.5f);
}
/// <summary>
/// 波形检测通过效果(绿色闪烁)
/// </summary>
public IEnumerator WaveformCheckPass(float duration = 0.4f)
{
yield return FlashWaveformColor(Color.green, duration);
}
/// <summary>
/// 波形检测失败/警告效果(红色闪烁)
/// </summary>
public IEnumerator WaveformCheckFail(float duration = 0.6f)
{
yield return FlashWaveformColor(new Color(1f, 0.2f, 0.2f), duration);
}
/// <summary>
/// 波形颜色闪烁效果
/// </summary>
public IEnumerator FlashWaveformColor(Color flashColor, float duration, int flashCount = 3)
{
if (primaryWaveform == null) yield break;
Color originalPrimary = GetCurrentWaveformPrimaryColor();
Color originalSecondary = GetCurrentWaveformSecondaryColor();
float flashTime = duration / (flashCount * 2);
for (int i = 0; i < flashCount; i++)
{
primaryWaveform.SetColors(flashColor, flashColor);
yield return new WaitForSeconds(flashTime);
primaryWaveform.SetColors(originalPrimary, originalSecondary);
yield return new WaitForSeconds(flashTime);
}
}
private Color GetCurrentWaveformPrimaryColor()
{
if (primaryWaveform == null || waveformConfig == null) return Color.white;
switch (primaryWaveform.WaveType)
{
case WaveformType.Logic: return waveformConfig.logicWaveform.primaryColor;
case WaveformType.Emotion: return waveformConfig.emotionWaveform.primaryColor;
case WaveformType.Joke: return waveformConfig.jokeWaveform.primaryColor;
case WaveformType.FilterOutput: return waveformConfig.filterOutputWaveform.primaryColor;
default: return Color.white;
}
}
private Color GetCurrentWaveformSecondaryColor()
{
if (primaryWaveform == null || waveformConfig == null) return Color.gray;
switch (primaryWaveform.WaveType)
{
case WaveformType.Logic: return waveformConfig.logicWaveform.secondaryColor;
case WaveformType.Emotion: return waveformConfig.emotionWaveform.secondaryColor;
case WaveformType.Joke: return waveformConfig.jokeWaveform.secondaryColor;
case WaveformType.FilterOutput: return waveformConfig.filterOutputWaveform.secondaryColor;
default: return Color.gray;
}
}
#endregion
#region 辅助方法
private void PlaySound(AudioClip clip)
{
if (_audioSource != null && clip != null)
{
_audioSource.PlayOneShot(clip);
}
}
#endregion
}
}