1030 lines
35 KiB
C#
1030 lines
35 KiB
C#
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
|
||
}
|
||
}
|