Files
aibis-dream/Assets/Scripts/MiniGame/HuoShan/AnalysisSystem/AnalysisModeManager.cs
T
2025-12-11 18:27:51 +08:00

1764 lines
65 KiB
C#
Raw 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 UnityEngine;
using UnityEngine.UI;
using TMPro;
using System.Collections.Generic;
using System.Linq;
using DG.Tweening;
using Shapes;
using AibisDream.FixSystem;
using AibisDream.Framework;
using Cinemachine;
namespace AibisDream.MiniGame.Language
{
/// <summary>
/// 情绪类型
/// </summary>
public enum EmotionType
{
Joy, // 快乐 - 15Hz
Sadness, // 悲伤 - 40Hz
Anger, // 愤怒 - 65Hz
Fear, // 恐惧 - 90Hz
Neutral // 中性/噪音
}
/// <summary>
/// 情绪配置
/// </summary>
[System.Serializable]
public class EmotionConfig
{
public EmotionType type;
public float frequency; // 0-100 的频率值
public Color color;
public string displayName;
public static EmotionConfig Joy => new EmotionConfig
{
type = EmotionType.Joy,
frequency = 15f,
color = new Color(1f, 1f, 0f, 1f),
displayName = "JOY"
};
public static EmotionConfig Sadness => new EmotionConfig
{
type = EmotionType.Sadness,
frequency = 40f,
color = new Color(0f, 0.67f, 1f, 1f),
displayName = "SADNESS"
};
public static EmotionConfig Anger => new EmotionConfig
{
type = EmotionType.Anger,
frequency = 65f,
color = new Color(1f, 0.2f, 0.2f, 1f),
displayName = "ANGER"
};
public static EmotionConfig Fear => new EmotionConfig
{
type = EmotionType.Fear,
frequency = 90f,
color = new Color(0.67f, 0f, 1f, 1f),
displayName = "FEAR"
};
public static EmotionConfig Neutral => new EmotionConfig
{
type = EmotionType.Neutral,
frequency = -1f,
color = new Color(0.5f, 0.5f, 0.5f, 1f),
displayName = "NOISE"
};
}
/// <summary>
/// 网格单元格数据
/// </summary>
[System.Serializable]
public class GridCell
{
public string word; // 词语内容
public EmotionType emotion;
public bool isTarget; // 是否是目标词
public int row;
public int col;
}
/// <summary>
/// 解析目标配置
/// </summary>
[System.Serializable]
public class AnalysisTarget
{
public string name;
public EmotionType emotion;
public List<TargetWord> words = new List<TargetWord>();
public Vector2Int stripPosition; // 解码器条纹正确位置(行,列)
public bool isCorrectAnswer; // 是否是正确答案
}
/// <summary>
/// 目标词配置
/// </summary>
[System.Serializable]
public class TargetWord
{
public Vector2Int position; // 在网格中的位置(行,列)
public string word; // 词语内容
}
/// <summary>
/// 粒子视觉状态(用于平滑过渡)
/// </summary>
public class ParticleVisualState
{
public float targetAlpha = 1f;
public float currentAlpha = 1f;
public Color targetColor = Color.white;
public Color currentColor = Color.white;
public float targetScale = 1f;
public float currentScale = 1f;
public Vector3 basePosition;
public Vector3 jitterOffset = Vector3.zero;
public bool isShowingWord = false; // true = word, false = cover
public Tweener alphaTween;
public Tweener colorTween;
public Tweener scaleTween;
public Tweener jitterTween;
}
/// <summary>
/// 分析模式管理器
/// </summary>
public class AnalysisModeManager : MonoBehaviour
{
public BubbleSlotGroup bubbleSlotGroup;
[Header("Canvas 引用(共享)")]
[SerializeField] private Canvas sharedCanvas; // 共享 LanguageParticleManager 的 canvas
[Header("UI 引用")]
[SerializeField] private RectTransform analysisPanel;
[SerializeField] private Button initializeButton;
[SerializeField] private Slider emotionSlider;
[SerializeField] private TMP_Text frequencyText;
[SerializeField] private TMP_Text signalStrengthText;
[SerializeField] private Button startFilterButton;
[SerializeField] private Button verifyButton;
[Header("网格配置(舞台布局)")]
[Tooltip("可选:用于定位网格中心的容器")]
[SerializeField] private RectTransform gridContainer;
[Tooltip("网格宽度(列数)")]
[SerializeField] private int gridWidth = 20;
[Tooltip("网格高度(行数)")]
[SerializeField] private int gridHeight = 20;
[Tooltip("单元格大小(Unity单位)")]
[SerializeField] private float cellSize = 0.4f;
[Tooltip("单元格间距(Unity单位)")]
[SerializeField] private float cellGap = 0.05f;
[Tooltip("文字大小")]
[SerializeField] private float cellFontSize = 2.5f;
[Tooltip("网格中心偏移量")]
[SerializeField] private Vector3 gridCenterOffset = Vector3.zero;
[Header("字体配置")]
[Tooltip("中文字体资源(用于网格文字)")]
[SerializeField] private TMP_FontAsset chineseFontAsset;
[Header("动画配置")]
[Tooltip("视觉效果过渡时长(颜色、透明度等)")]
[SerializeField] private float visualTransitionDuration = 0.15f;
[Tooltip("闪烁效果间隔")]
[SerializeField] private float glitchInterval = 0.1f;
[Tooltip("抖动强度")]
[SerializeField] private float jitterIntensity = 0.002f;
[Header("网格样式")]
[SerializeField] private Color gridLineColor = new Color(1f, 1f, 1f, 0.1f);
[SerializeField] private float gridThickness = 0.02f;
[Header("关卡配置(数据驱动)")]
[Tooltip("默认关卡配置数据(ScriptableObject)\n也可以通过代码动态加载其他关卡")]
[SerializeField] private AnalysisModeData currentLevelData;
// 运行时数据
private List<AnalysisTarget> analysisTargets = new List<AnalysisTarget>();
private AnalysisModeGenerator generator;
private int stripWidth; // 从关卡数据动态获取
private int stripHeight; // 从关卡数据动态获取
// 状态
private bool isAnalysisModeActive = false;
private bool isFilteringActive = false;
private float currentFrequency = 50f;
private GridCell[,] gridData;
private Dictionary<Vector2Int, TextParticle> gridParticles = new Dictionary<Vector2Int, TextParticle>();
private bool isInitialized = false;
private string completionNodeName = "";
// 平滑过渡状态存储
private Dictionary<Vector2Int, ParticleVisualState> particleStates = new Dictionary<Vector2Int, ParticleVisualState>();
// 闪烁计时器(用于平滑闪烁)
private Dictionary<Vector2Int, float> glitchTimers = new Dictionary<Vector2Int, float>();
// Shapes 格子绘制器
private GameObject mainGridDrawerObj;
private GameObject decoderGridDrawerObj;
private Vector3 gridStartPos; // 网格起始位置(左上角)
// 解码器相关
private Dictionary<Vector2Int, TextParticle> decoderParticles = new Dictionary<Vector2Int, TextParticle>();
private Vector2Int currentDecoderGridPos; // 解码器在主网格中的位置(行,列)
private bool isDraggingDecoder = false;
private Vector3 dragStartMouseWorldPos; // 拖拽开始时的鼠标世界坐标
private Vector3 dragStartDecoderWorldPos; // 拖拽开始时的解码器世界坐标
private Vector2Int dragStartGridPos; // 拖拽开始时的解码器网格坐标
private void Start()
{
// 注册到系统字典
FixSystemCenter.SystemDic.Register(this);
// 注册相机
var virtualCam = transform.Find("Analysis Camera")?.GetComponent<ICinemachineCamera>();
if (virtualCam != null)
{
CameraKit.Instance.RegisterCamera(CameraEnum.AnalysisMode, virtualCam);
}
SetupUI();
}
private void OnDestroy()
{
CameraKit.Instance?.UnRegisterCamera(CameraEnum.AnalysisMode);
CameraKit.Instance?.UnRegisterCamera(CameraEnum.AnalysisModeDeep);
}
/// <summary>
/// 进入分析模式(由状态机调用)
/// </summary>
public void OnEnter()
{
// 开启分析模式视图
// if (!isAnalysisModeActive)
// {
// OpenView();
// }
}
/// <summary>
/// 退出分析模式(由状态机调用)
/// </summary>
public void OnExit()
{
// 关闭分析模式视图
if (isAnalysisModeActive)
{
CloseView();
}
}
private void SetupUI()
{
if (analysisPanel != null)
{
analysisPanel.gameObject.SetActive(false);
}
if (initializeButton != null)
{
initializeButton.onClick.AddListener(OnInitializeClicked);
// 初始化按钮默认可用
initializeButton.interactable = true;
}
if (startFilterButton != null)
{
startFilterButton.onClick.AddListener(OnStartFilterClicked);
// 过滤按钮在初始化前不可用
startFilterButton.interactable = false;
}
if (verifyButton != null)
{
verifyButton.onClick.AddListener(OnVerifyClicked);
verifyButton.interactable = false;
}
if (emotionSlider != null)
{
emotionSlider.minValue = 0f;
emotionSlider.maxValue = 100f;
emotionSlider.value = 50f;
emotionSlider.onValueChanged.AddListener(OnEmotionSliderChanged);
// 滑动条在过滤模式开启前不可用
emotionSlider.interactable = false;
}
}
/// <summary>
/// 加载关卡数据(数据驱动)
/// </summary>
private void LoadLevelData(AnalysisModeData levelData)
{
if (levelData == null)
{
Debug.LogError("[AnalysisModeManager] 关卡数据为空!");
return;
}
// 验证配置
if (!levelData.Validate(out string errorMessage))
{
Debug.LogError($"[AnalysisModeManager] 关卡配置无效: {errorMessage}");
return;
}
currentLevelData = levelData;
// 创建生成器
generator = new AnalysisModeGenerator(levelData);
// 清空目标列表
analysisTargets.Clear();
// 使用生成器创建目标
AnalysisTarget target = generator.GenerateAnalysisTarget();
analysisTargets.Add(target);
// 更新解码器尺寸
stripWidth = levelData.decoderWidth;
stripHeight = levelData.decoderHeight;
Debug.Log($"[AnalysisModeManager] 已加载关卡: {levelData.levelName}");
}
/// <summary>
/// 启动分析模式(独立系统入口)
/// </summary>
/// <param name="levelData">关卡配置数据</param>
/// <param name="completionNode">完成回调节点(可选,会覆盖配置中的节点)</param>
public void OpenView(AnalysisModeData levelData = null, string completionNode = null)
{
if (isAnalysisModeActive) return;
// 加载关卡数据
if (levelData != null)
{
LoadLevelData(levelData);
}
else if (currentLevelData != null)
{
// 使用默认配置
LoadLevelData(currentLevelData);
}
else
{
Debug.LogError("[AnalysisModeManager] 没有可用的关卡数据!");
return;
}
// 设置完成回调节点(优先使用参数,其次使用配置)
completionNodeName = !string.IsNullOrEmpty(completionNode)
? completionNode
: currentLevelData.successNodeName;
// 显示分析控制面板
isAnalysisModeActive = true;
if (analysisPanel != null)
{
analysisPanel.gameObject.SetActive(true);
}
// 自动初始化网格
if (!isInitialized)
{
InitializeGrid();
}
}
/// <summary>
/// 关闭分析模式
/// </summary>
public void CloseView()
{
if (!isAnalysisModeActive) return;
isAnalysisModeActive = false;
// 重置状态
isFilteringActive = false;
isInitialized = false;
currentFrequency = 50f;
ClearGrid();
}
private void OnInitializeClicked()
{
// 只能初始化一次
if (isInitialized)
{
return;
}
// 禁用初始化按钮
if (initializeButton != null)
{
initializeButton.interactable = false;
}
InitializeGrid();
}
private void InitializeGrid()
{
if (currentLevelData == null || generator == null)
{
Debug.LogError("[AnalysisModeManager] 未加载关卡数据或生成器未初始化!");
return;
}
// 使用生成器创建网格数据(数据驱动,智能生成)
gridData = generator.GenerateGrid(gridWidth, gridHeight);
// 创建网格 UI(带动画)
StartCoroutine(AnimateGridCreation());
}
private System.Collections.IEnumerator AnimateGridCreation()
{
ClearGrid();
// 计算网格中心位置
Vector3 gridCenter = Vector3.zero;
if (sharedCanvas != null)
{
gridCenter = sharedCanvas.transform.position + gridCenterOffset;
}
// 计算网格总大小
float totalWidth = gridWidth * cellSize + (gridWidth - 1) * cellGap;
float totalHeight = gridHeight * cellSize + (gridHeight - 1) * cellGap;
// 计算起始位置(左上角)
gridStartPos = gridCenter - new Vector3(totalWidth / 2f, totalHeight / 2f, 0f);
// 创建主矩阵 Shapes 格子绘制器
CreateMainGridDrawer();
// 随机顺序创建单元格(Matrix 风格)
List<Vector2Int> cellPositions = new List<Vector2Int>();
for (int row = 0; row < gridHeight; row++)
{
for (int col = 0; col < gridWidth; col++)
{
cellPositions.Add(new Vector2Int(row, col));
}
}
cellPositions = cellPositions.OrderBy(x => Random.value).ToList();
int batchSize = 10;
for (int i = 0; i < cellPositions.Count; i++)
{
var pos = cellPositions[i];
CreateGridParticle(pos.x, pos.y, gridStartPos);
if (i % batchSize == 0)
{
yield return new WaitForSeconds(0.01f);
}
}
yield return new WaitForSeconds(0.5f);
isInitialized = true;
if (startFilterButton != null)
{
startFilterButton.interactable = true;
}
}
/// <summary>
/// 创建主矩阵 Shapes 格子绘制器
/// </summary>
private void CreateMainGridDrawer()
{
if (sharedCanvas == null) return;
// 清理旧的绘制器
if (mainGridDrawerObj != null)
{
Destroy(mainGridDrawerObj);
}
// 创建绘制器对象
mainGridDrawerObj = new GameObject("MainGridDrawer");
// 保持在 Canvas 下
mainGridDrawerObj.transform.SetParent(sharedCanvas.transform, false);
// 设置位置到网格起始点(左上角),并保持旋转一致
mainGridDrawerObj.transform.position = gridStartPos;
mainGridDrawerObj.transform.rotation = sharedCanvas.transform.rotation;
mainGridDrawerObj.transform.localScale = Vector3.one;
// 添加 RectTransformCanvas 下通常需要,虽然 Shapes 主要用 Transform
RectTransform rect = mainGridDrawerObj.AddComponent<RectTransform>();
rect.anchorMin = new Vector2(0.5f, 0.5f);
rect.anchorMax = new Vector2(0.5f, 0.5f);
rect.sizeDelta = Vector2.zero;
// 计算步长
float stride = cellSize + cellGap;
float halfStride = stride / 2f;
// 创建网格容器(避免 Hierarchy 太乱)
GameObject linesContainer = new GameObject("Lines");
linesContainer.transform.SetParent(mainGridDrawerObj.transform, false);
linesContainer.transform.localPosition = Vector3.zero;
// 绘制水平线 (Rows + 1)
// Y轴向下为负,Row 0 中心在 0,上方边界在 +halfStride
float startX = -halfStride;
float endX = (gridWidth * stride) - halfStride;
for (int row = 0; row <= gridHeight; row++)
{
float yPos = -(row * stride) + halfStride;
CreateGridLine(linesContainer.transform, new Vector3(startX, yPos, 0), new Vector3(endX, yPos, 0));
}
// 绘制垂直线 (Cols + 1)
// X轴向右为正,Col 0 中心在 0,左方边界在 -halfStride
float startY = halfStride;
float endY = -(gridHeight * stride) + halfStride;
for (int col = 0; col <= gridWidth; col++)
{
float xPos = (col * stride) - halfStride;
CreateGridLine(linesContainer.transform, new Vector3(xPos, startY, 0), new Vector3(xPos, endY, 0));
}
}
private void CreateGridLine(Transform parent, Vector3 start, Vector3 end)
{
GameObject lineObj = new GameObject("GridLine");
lineObj.transform.SetParent(parent, false);
Line line = lineObj.AddComponent<Line>();
line.Start = start;
line.End = end;
line.Color = gridLineColor;
line.Thickness = gridThickness;
line.ThicknessSpace = ThicknessSpace.Meters; // 或者 Pixels,取决于需求,通常 World Space 用 Meters
line.SortingOrder = 5;
// 确保渲染顺序正确(如果是在 Overlay Canvas 下,Z轴可能无效,需要 SortingOrder
// Shapes 通常会自动处理,但在 UI 中可能需要调整 Z
line.transform.localPosition = Vector3.zero;
}
/// <summary>
/// 创建解码器
/// </summary>
private void CreateDecoder()
{
if (currentLevelData == null || sharedCanvas == null) return;
// 清理旧的解码器
ClearDecoder();
// 初始化解码器位置(网格坐标,默认在中心附近)
currentDecoderGridPos = new Vector2Int(
gridHeight / 2 - stripHeight / 2,
gridWidth / 2 - stripWidth / 2
);
// 创建解码器网格绘制器
CreateDecoderGridDrawer();
// 创建解码器文字粒子
CreateDecoderParticles();
// 更新解码器位置
UpdateDecoderPosition();
}
/// <summary>
/// 创建解码器网格绘制器(类似MainGridDrawer
/// </summary>
private void CreateDecoderGridDrawer()
{
if (sharedCanvas == null) return;
// 创建绘制器对象
decoderGridDrawerObj = new GameObject("DecoderGridDrawer");
decoderGridDrawerObj.transform.SetParent(sharedCanvas.transform, false);
// 设置初始位置
decoderGridDrawerObj.transform.rotation = sharedCanvas.transform.rotation;
decoderGridDrawerObj.transform.localScale = Vector3.one;
// 添加 RectTransform
RectTransform rect = decoderGridDrawerObj.AddComponent<RectTransform>();
rect.anchorMin = new Vector2(0.5f, 0.5f);
rect.anchorMax = new Vector2(0.5f, 0.5f);
rect.sizeDelta = Vector2.zero;
// 计算步长(与主网格一致)
float stride = cellSize + cellGap;
float halfStride = stride / 2f;
// 创建网格容器
GameObject linesContainer = new GameObject("Lines");
linesContainer.transform.SetParent(decoderGridDrawerObj.transform, false);
linesContainer.transform.localPosition = Vector3.zero;
// 绘制水平线 (stripHeight + 1 条)
float startX = -halfStride;
float endX = (stripWidth * stride) - halfStride;
for (int row = 0; row <= stripHeight; row++)
{
float yPos = -(row * stride) + halfStride;
CreateDecoderGridLine(linesContainer.transform, new Vector3(startX, yPos, 0), new Vector3(endX, yPos, 0));
}
// 绘制垂直线 (stripWidth + 1 条)
float startY = halfStride;
float endY = -(stripHeight * stride) + halfStride;
for (int col = 0; col <= stripWidth; col++)
{
float xPos = (col * stride) - halfStride;
CreateDecoderGridLine(linesContainer.transform, new Vector3(xPos, startY, 0), new Vector3(xPos, endY, 0));
}
// 添加拖拽功能
AddDecoderDragHandler();
}
/// <summary>
/// 创建解码器网格线(与主网格不同的颜色和渲染顺序)
/// </summary>
private void CreateDecoderGridLine(Transform parent, Vector3 start, Vector3 end)
{
GameObject lineObj = new GameObject("DecoderGridLine");
lineObj.transform.SetParent(parent, false);
Line line = lineObj.AddComponent<Line>();
line.Start = start;
line.End = end;
// 使用 cyan 颜色(参考 web 版本的 border: 1px solid cyan
line.Color = new Color(0f, 1f, 1f, 0.8f); // Cyan 青色,较高不透明度
line.Thickness = gridThickness * 1.5f; // 稍微粗一些
line.ThicknessSpace = ThicknessSpace.Meters;
line.SortingOrder = 10; // 更高的渲染顺序,显示在主网格之上
line.transform.localPosition = Vector3.zero;
}
/// <summary>
/// 添加解码器拖拽处理器
/// </summary>
private void AddDecoderDragHandler()
{
if (decoderGridDrawerObj == null) return;
// 创建一个透明的Image作为拖拽区域
GameObject dragAreaObj = new GameObject("DragArea");
dragAreaObj.transform.SetParent(decoderGridDrawerObj.transform, false);
RectTransform dragRect = dragAreaObj.AddComponent<RectTransform>();
dragRect.anchorMin = Vector2.zero;
dragRect.anchorMax = Vector2.one;
dragRect.offsetMin = Vector2.zero;
dragRect.offsetMax = Vector2.zero;
float stride = cellSize + cellGap;
dragRect.sizeDelta = new Vector2(stripWidth * stride, stripHeight * stride);
dragRect.anchoredPosition = new Vector2(
(stripWidth * stride) / 2f,
-(stripHeight * stride) / 2f
);
// 添加半透明背景,让解码器区域更明显,同时也是拖拽响应区域
Image dragImage = dragAreaObj.AddComponent<Image>();
dragImage.color = new Color(0f, 0f, 0f, 0.1f); // 极淡的半透明背景,主要用于拖拽响应
dragImage.raycastTarget = true;
// 添加 EventTrigger 组件用于处理鼠标事件
UnityEngine.EventSystems.EventTrigger eventTrigger = dragAreaObj.AddComponent<UnityEngine.EventSystems.EventTrigger>();
// 开始拖拽
UnityEngine.EventSystems.EventTrigger.Entry beginDragEntry = new UnityEngine.EventSystems.EventTrigger.Entry();
beginDragEntry.eventID = UnityEngine.EventSystems.EventTriggerType.BeginDrag;
beginDragEntry.callback.AddListener((data) => { OnDecoderBeginDrag((UnityEngine.EventSystems.PointerEventData)data); });
eventTrigger.triggers.Add(beginDragEntry);
// 拖拽中
UnityEngine.EventSystems.EventTrigger.Entry dragEntry = new UnityEngine.EventSystems.EventTrigger.Entry();
dragEntry.eventID = UnityEngine.EventSystems.EventTriggerType.Drag;
dragEntry.callback.AddListener((data) => { OnDecoderDrag((UnityEngine.EventSystems.PointerEventData)data); });
eventTrigger.triggers.Add(dragEntry);
// 结束拖拽
UnityEngine.EventSystems.EventTrigger.Entry endDragEntry = new UnityEngine.EventSystems.EventTrigger.Entry();
endDragEntry.eventID = UnityEngine.EventSystems.EventTriggerType.EndDrag;
endDragEntry.callback.AddListener((data) => { OnDecoderEndDrag((UnityEngine.EventSystems.PointerEventData)data); });
eventTrigger.triggers.Add(endDragEntry);
}
/// <summary>
/// 创建解码器文字粒子
/// </summary>
private void CreateDecoderParticles()
{
if (currentLevelData == null || sharedCanvas == null) return;
// 解析解码器文字
string decoderText = currentLevelData.decoderText;
List<string> words = SplitIntoTwoCharChunks(decoderText);
int wordIndex = 0;
// 创建HashSet用于快速查找孔洞
HashSet<Vector2Int> holes = new HashSet<Vector2Int>(currentLevelData.holePositions);
for (int row = 0; row < stripHeight; row++)
{
for (int col = 0; col < stripWidth; col++)
{
Vector2Int localPos = new Vector2Int(row, col);
// 如果是孔洞,跳过
if (holes.Contains(localPos))
{
continue;
}
// 如果还有词可以显示
if (wordIndex < words.Count)
{
CreateDecoderParticle(row, col, words[wordIndex]);
wordIndex++;
}
}
}
}
/// <summary>
/// 创建单个解码器粒子
/// </summary>
private void CreateDecoderParticle(int row, int col, string word)
{
if (sharedCanvas == null) return;
// 先创建背景方块(使用 Shapes)
CreateDecoderCellBackground(row, col);
// 创建粒子对象
GameObject particleObj = new GameObject($"DecoderParticle_{row}_{col}");
// 添加 RectTransform
RectTransform rectTransform = particleObj.AddComponent<RectTransform>();
rectTransform.SetParent(decoderGridDrawerObj.transform, false);
rectTransform.localScale = Vector3.one;
rectTransform.sizeDelta = Vector2.zero;
// 计算局部位置(相对于解码器左上角)
float stride = cellSize + cellGap;
float xPos = col * stride;
float yPos = -row * stride;
rectTransform.localPosition = new Vector3(xPos, yPos, -0.15f); // 在背景之上
// 添加 TextParticle 组件
TextParticle particle = particleObj.AddComponent<TextParticle>();
particle.SetFont(chineseFontAsset);
// 设置为完全静态
particle.isStatic = true;
particle.isCalmed = true;
particle.velocity = Vector2.zero;
// 设置透明度
particle.alpha = 1f;
particle.baseAlpha = 1f;
// 设置文字内容
particle.ForceSetCharacter(word);
// 设置字体大小和颜色(参考 web 版本的 cyan 颜色)
if (particle.textMesh != null)
{
particle.textMesh.fontSize = cellFontSize;
// 使用 cyan 颜色(0, 255, 255)参考 web 版本
particle.textMesh.color = new Color(0f, 1f, 1f, 1f); // Cyan 青色
particle.textMesh.fontStyle = FontStyles.Bold;
}
// 添加发光效果(参考 web 版本的 text-shadow: 0 0 8px cyan
if (particle.glowSprite != null)
{
particle.glowSprite.color = new Color(0f, 1f, 1f, 0.6f); // Cyan 发光
}
Vector2Int pos = new Vector2Int(row, col);
decoderParticles[pos] = particle;
}
/// <summary>
/// 创建解码器单元格背景(遮挡主网格文字)
/// </summary>
private void CreateDecoderCellBackground(int row, int col)
{
if (decoderGridDrawerObj == null) return;
// 创建背景对象
GameObject bgObj = new GameObject($"DecoderBg_{row}_{col}");
bgObj.transform.SetParent(decoderGridDrawerObj.transform, false);
// 计算局部位置(相对于解码器左上角)
float stride = cellSize + cellGap;
float xPos = col * stride;
float yPos = -row * stride;
bgObj.transform.localPosition = new Vector3(xPos, yPos, -0.1f); // 更靠前的 Z 轴,确保遮挡主网格
// 添加 Shapes Rectangle 组件
Shapes.Rectangle rect = bgObj.AddComponent<Shapes.Rectangle>();
// 背景大小应该填充整个格子区域(包括间隙),确保完全遮挡下方文字
rect.Width = stride;
rect.Height = stride;
// 使用深青色背景,80% 不透明度
rect.Color = new Color(0f, 0.12f, 0.12f, 0.8f);
rect.SortingOrder = 8; // 在主网格之上,在解码器网格线之下
rect.CornerRadii = Vector4.one * 0.02f; // 轻微圆角
// 设置为不透明渲染模式
rect.BlendMode = Shapes.ShapesBlendMode.Opaque;
}
/// <summary>
/// 更新解码器位置(对齐到网格)
/// </summary>
private void UpdateDecoderPosition()
{
if (decoderGridDrawerObj == null) return;
// 计算解码器的世界位置
float stride = cellSize + cellGap;
Vector3 decoderWorldPos = gridStartPos + new Vector3(
currentDecoderGridPos.y * stride,
-currentDecoderGridPos.x * stride,
-0.1f // Z轴稍微往前,确保显示在主网格之上
);
decoderGridDrawerObj.transform.position = decoderWorldPos;
}
/// <summary>
/// 开始拖拽解码器
/// </summary>
private void OnDecoderBeginDrag(UnityEngine.EventSystems.PointerEventData eventData)
{
isDraggingDecoder = true;
dragStartGridPos = currentDecoderGridPos;
// 记录鼠标的世界坐标
if (RectTransformUtility.ScreenPointToWorldPointInRectangle(
sharedCanvas.transform as RectTransform,
eventData.position,
eventData.pressEventCamera,
out dragStartMouseWorldPos))
{
// 记录解码器的世界坐标
if (decoderGridDrawerObj != null)
{
dragStartDecoderWorldPos = decoderGridDrawerObj.transform.position;
}
}
}
/// <summary>
/// 拖拽解码器中
/// </summary>
private void OnDecoderDrag(UnityEngine.EventSystems.PointerEventData eventData)
{
if (!isDraggingDecoder || decoderGridDrawerObj == null) return;
// 获取当前鼠标的世界坐标
Vector3 currentMouseWorldPos;
if (RectTransformUtility.ScreenPointToWorldPointInRectangle(
sharedCanvas.transform as RectTransform,
eventData.position,
eventData.pressEventCamera,
out currentMouseWorldPos))
{
// 计算鼠标移动的增量(世界坐标)
Vector3 mouseDelta = currentMouseWorldPos - dragStartMouseWorldPos;
// 计算解码器应该在的世界坐标(保持鼠标和解码器的相对位置)
Vector3 targetDecoderWorldPos = dragStartDecoderWorldPos + mouseDelta;
// 将目标世界坐标转换为网格坐标
Vector3 offsetFromGridStart = targetDecoderWorldPos - gridStartPos;
float stride = cellSize + cellGap;
int targetGridRow = Mathf.RoundToInt(-offsetFromGridStart.y / stride); // Y轴反向
int targetGridCol = Mathf.RoundToInt(offsetFromGridStart.x / stride);
// 限制在主网格范围内
targetGridRow = Mathf.Clamp(targetGridRow, 0, gridHeight - stripHeight);
targetGridCol = Mathf.Clamp(targetGridCol, 0, gridWidth - stripWidth);
Vector2Int newGridPos = new Vector2Int(targetGridRow, targetGridCol);
// 如果位置改变,更新解码器
if (newGridPos != currentDecoderGridPos)
{
currentDecoderGridPos = newGridPos;
UpdateDecoderPosition();
}
}
}
/// <summary>
/// 结束拖拽解码器
/// </summary>
private void OnDecoderEndDrag(UnityEngine.EventSystems.PointerEventData eventData)
{
isDraggingDecoder = false;
// 确保最终位置对齐到网格
UpdateDecoderPosition();
}
/// <summary>
/// 清理解码器
/// </summary>
private void ClearDecoder()
{
// 清理解码器粒子
foreach (var particle in decoderParticles.Values)
{
if (particle != null)
{
Destroy(particle.gameObject);
}
}
decoderParticles.Clear();
// 清理解码器绘制器
if (decoderGridDrawerObj != null)
{
Destroy(decoderGridDrawerObj);
decoderGridDrawerObj = null;
}
}
/// <summary>
/// 创建网格粒子(直接使用 TextParticle,与 LanguageParticleManager 一致)
/// </summary>
private void CreateGridParticle(int row, int col, Vector3 gridStartPos)
{
if (sharedCanvas == null) return;
// 计算粒子位置(从左上角开始)
float xPos = col * (cellSize + cellGap);
float yPos = -row * (cellSize + cellGap); // Y 轴向下
Vector3 position = gridStartPos + new Vector3(xPos, yPos, 0f);
// 创建粒子对象(参考 LanguageParticleManager.CreateParticleObject
GameObject particleObj = new GameObject($"GridParticle_{row}_{col}");
// 添加 RectTransform(在 Canvas 下必需)
RectTransform rectTransform = particleObj.AddComponent<RectTransform>();
rectTransform.SetParent(sharedCanvas.transform, false);
rectTransform.position = position;
rectTransform.localScale = Vector3.one;
rectTransform.sizeDelta = Vector2.zero;
// 添加 TextParticle 组件
TextParticle particle = particleObj.AddComponent<TextParticle>();
particle.SetFont(chineseFontAsset);
// 设置为完全静态(固定在格子里,不飘动)
particle.isStatic = true; // 静态粒子
particle.isCalmed = true; // 完全平静,不受力影响
particle.velocity = Vector2.zero; // 速度为0
// 重要:设置透明度,否则粒子不可见!
particle.alpha = 1f;
particle.baseAlpha = 1f;
// 设置文字内容
if (gridData != null)
{
GridCell cellData = gridData[row, col];
particle.ForceSetCharacter(cellData.word); // 显示词语
// 设置字体大小和颜色
if (particle.textMesh != null)
{
particle.textMesh.fontSize = cellFontSize;
particle.textMesh.color = Color.white;
}
}
Vector2Int pos = new Vector2Int(row, col);
gridParticles[pos] = particle;
// 初始化视觉状态
if (!particleStates.ContainsKey(pos))
{
particleStates[pos] = new ParticleVisualState
{
basePosition = position,
currentAlpha = 1f,
targetAlpha = 1f,
currentColor = Color.white,
targetColor = Color.white,
currentScale = 1f,
targetScale = 1f,
isShowingWord = true
};
}
// 初始化闪烁计时器
if (!glitchTimers.ContainsKey(pos))
{
glitchTimers[pos] = Random.Range(0f, glitchInterval);
}
}
private void ClearGrid()
{
// 清理所有Tween
foreach (var state in particleStates.Values)
{
if (state.alphaTween != null) state.alphaTween.Kill();
if (state.colorTween != null) state.colorTween.Kill();
if (state.scaleTween != null) state.scaleTween.Kill();
if (state.jitterTween != null) state.jitterTween.Kill();
}
foreach (var particle in gridParticles.Values)
{
if (particle != null)
{
Destroy(particle.gameObject);
}
}
gridParticles.Clear();
particleStates.Clear();
glitchTimers.Clear();
if (mainGridDrawerObj != null)
{
Destroy(mainGridDrawerObj);
mainGridDrawerObj = null;
}
// 同时清理解码器
ClearDecoder();
}
private List<string> SplitIntoTwoCharChunks(string text)
{
List<string> chunks = new List<string>();
for (int i = 0; i < text.Length; i += 2)
{
if (i + 1 < text.Length)
{
chunks.Add(text.Substring(i, 2));
}
else
{
chunks.Add(text[i] + " "); // 奇数长度时补空格
}
}
return chunks;
}
private void OnStartFilterClicked()
{
if (!isInitialized || isFilteringActive) return;
isFilteringActive = true;
// 显示解码器条纹
CreateDecoder();
// 启用滑动条和验证按钮
if (emotionSlider != null)
{
emotionSlider.interactable = true;
}
if (verifyButton != null)
{
verifyButton.interactable = true;
}
if (startFilterButton != null)
{
startFilterButton.interactable = false;
}
// 应用初始过滤效果
UpdateGridVisualsWithFilter();
}
private void OnEmotionSliderChanged(float value)
{
currentFrequency = value;
if (frequencyText != null)
{
frequencyText.text = $"{value:F1} Hz";
// 确保频率文本使用正确的字体和大小
if (chineseFontAsset != null && frequencyText.font != chineseFontAsset)
{
frequencyText.font = chineseFontAsset;
}
}
// 更新网格显示
if (isFilteringActive)
{
UpdateGridVisualsWithFilter();
}
}
private void Update()
{
// 在过滤模式下,持续更新视觉效果以确保闪烁效果平滑
// 注意:只在闪烁区域需要频繁更新,其他区域DOTween会自动处理过渡
if (isFilteringActive && isInitialized)
{
// 只更新处于闪烁状态的粒子(距离在5-20之间)
// 这样可以减少性能开销
bool needsUpdate = false;
foreach (var kvp in gridParticles)
{
Vector2Int pos = kvp.Key;
if (gridData == null || pos.x >= gridHeight || pos.y >= gridWidth) continue;
GridCell cellData = gridData[pos.x, pos.y];
if (cellData.emotion == EmotionType.Neutral) continue;
EmotionConfig emotion = GetEmotionConfig(cellData.emotion);
float distance = CalculateFrequencyDistance(emotion);
// 如果处于闪烁区域,需要持续更新
if (distance >= 5f && distance < 20f)
{
needsUpdate = true;
break;
}
}
// 如果有粒子处于闪烁状态,每帧更新(但限制更新频率)
if (needsUpdate)
{
UpdateGridVisualsWithFilter();
}
}
}
private void UpdateGridVisualsWithFilter()
{
if (gridData == null) return;
float maxSignal = 0f;
for (int row = 0; row < gridHeight; row++)
{
for (int col = 0; col < gridWidth; col++)
{
float cellSignal = UpdateCellVisual(row, col);
maxSignal = Mathf.Max(maxSignal, cellSignal);
}
}
UpdateSignalStrengthDisplay(maxSignal);
}
/// <summary>
/// 更新单个单元格的视觉效果
/// </summary>
private float UpdateCellVisual(int row, int col)
{
Vector2Int pos = new Vector2Int(row, col);
if (!gridParticles.ContainsKey(pos)) return 0f;
TextParticle particle = gridParticles[pos];
if (particle == null || particle.textMesh == null) return 0f;
GridCell cellData = gridData[row, col];
EmotionConfig emotion = GetEmotionConfig(cellData.emotion);
// 计算频率距离
float distance = CalculateFrequencyDistance(emotion);
// 根据情绪类型应用不同的视觉效果
if (cellData.emotion == EmotionType.Neutral)
{
ApplyNeutralState(particle, cellData);
return 0f;
}
else
{
return ApplyEmotionState(particle, cellData, emotion, distance);
}
}
/// <summary>
/// 计算与当前频率的距离
/// </summary>
private float CalculateFrequencyDistance(EmotionConfig emotion)
{
if (emotion.type == EmotionType.Neutral)
{
return 100f;
}
return Mathf.Abs(currentFrequency - emotion.frequency);
}
/// <summary>
/// 应用情绪相关的视觉状态(参考 HTML 原型的视觉效果)
/// </summary>
private float ApplyEmotionState(TextParticle particle, GridCell cellData, EmotionConfig emotion, float distance)
{
bool isTargetWord = cellData.isTarget;
Vector2Int pos = new Vector2Int(cellData.row, cellData.col);
if (!particleStates.ContainsKey(pos))
{
particleStates[pos] = new ParticleVisualState
{
basePosition = particle.transform.position,
currentAlpha = particle.alpha,
targetAlpha = particle.alpha,
currentColor = particle.textMesh.color,
targetColor = particle.textMesh.color,
currentScale = 1f,
targetScale = 1f,
isShowingWord = true
};
}
ParticleVisualState state = particleStates[pos];
state.basePosition = particle.transform.position;
// 确保显示词语
if (particle.currentChar != cellData.word)
{
particle.ForceSetCharacter(cellData.word);
state.isShowingWord = true;
}
// 根据距离应用不同的视觉效果(参考 HTML)
if (distance < 5f)
{
// 锁定状态:高亮显示
return ApplyLockedStateSimple(particle, cellData, emotion, distance, state);
}
else if (distance < 20f)
{
// 闪烁状态
return ApplyGlitchingStateSimple(particle, cellData, emotion, distance, state);
}
else if (distance < 35f)
{
// 淡出状态
return ApplyFadingStateSimple(particle, cellData, distance, state);
}
else
{
// 隐藏状态
return ApplyHiddenStateSimple(particle, cellData, state);
}
}
/// <summary>
/// 锁定状态:频率匹配,高亮显示(参考 HTML)
/// </summary>
private float ApplyLockedStateSimple(TextParticle particle, GridCell cellData, EmotionConfig emotion, float distance, ParticleVisualState state)
{
// 计算信号强度
float signalStrength = 100f - distance * 20f;
signalStrength = Mathf.Clamp01(signalStrength / 100f);
// 目标值:高亮、放大、加粗
state.targetAlpha = 1f;
state.targetColor = emotion.color;
state.targetScale = 1.15f;
// 平滑过渡
SmoothTransition(state, particle, visualTransitionDuration);
// 应用样式
if (particle.textMesh != null)
{
particle.textMesh.fontStyle = FontStyles.Bold;
}
// 设置光晕
SetParticleGlow(particle, emotion.color, 0.3f + signalStrength * 0.4f);
// 停止抖动
if (state.jitterTween != null)
{
state.jitterTween.Kill();
state.jitterTween = null;
}
particle.transform.position = state.basePosition;
return signalStrength * 100f;
}
/// <summary>
/// 闪烁状态:颜色和光晕闪烁(参考 HTML)
/// </summary>
private float ApplyGlitchingStateSimple(TextParticle particle, GridCell cellData, EmotionConfig emotion, float distance, ParticleVisualState state)
{
Vector2Int pos = new Vector2Int(cellData.row, cellData.col);
// 计算闪烁强度
float glitchChance = 1f - (distance / 20f);
glitchChance = Mathf.Clamp01(glitchChance);
// 使用 Time.time 作为基础时间
if (!glitchTimers.ContainsKey(pos))
{
glitchTimers[pos] = Time.time + Random.Range(0f, glitchInterval * 2f);
}
// 颜色在情绪色和白色之间过渡(闪烁效果)
float timePhase = (Time.time - glitchTimers[pos]) * (10f + glitchChance * 20f);
float glitchPhase = Mathf.Sin(timePhase) * 0.5f + 0.5f;
float colorLerp = glitchChance * (0.5f + glitchPhase * 0.5f);
state.targetColor = Color.Lerp(Color.white, emotion.color, colorLerp);
// 目标值:轻微模糊、中等透明度
float blurFactor = (distance - 5f) / 15f;
state.targetAlpha = 0.9f - blurFactor * 0.2f;
state.targetScale = 1f;
// 平滑过渡
SmoothTransition(state, particle, visualTransitionDuration * 0.5f);
// 应用样式
if (particle.textMesh != null)
{
particle.textMesh.fontStyle = FontStyles.Normal;
}
// 设置光晕(闪烁效果)
float glowAlpha = 0.2f + glitchChance * 0.2f + glitchPhase * 0.1f;
SetParticleGlow(particle, emotion.color, glowAlpha);
// 添加抖动效果
float jitterAmount = glitchChance * jitterIntensity;
Vector3 jitterOffset = new Vector3(
Random.Range(-jitterAmount, jitterAmount),
Random.Range(-jitterAmount, jitterAmount),
0f
);
particle.transform.position = state.basePosition + jitterOffset;
return 0f;
}
/// <summary>
/// 淡出状态:逐渐淡化(参考 HTML)
/// </summary>
private float ApplyFadingStateSimple(TextParticle particle, GridCell cellData, float distance, ParticleVisualState state)
{
// 计算透明度
float alpha = 0.6f - ((distance - 20f) / 30f);
alpha = Mathf.Clamp01(alpha);
state.targetAlpha = alpha;
state.targetColor = Color.white;
state.targetScale = 1f;
// 平滑过渡
SmoothTransition(state, particle, visualTransitionDuration);
// 应用样式
if (particle.textMesh != null)
{
particle.textMesh.fontStyle = FontStyles.Normal;
}
// 清除光晕
SetParticleGlow(particle, Color.clear, 0f);
// 停止抖动
if (state.jitterTween != null)
{
state.jitterTween.Kill();
state.jitterTween = null;
}
particle.transform.position = state.basePosition;
return 0f;
}
/// <summary>
/// 隐藏状态:几乎不可见(参考 HTML)
/// </summary>
private float ApplyHiddenStateSimple(TextParticle particle, GridCell cellData, ParticleVisualState state)
{
state.targetAlpha = 0.05f;
state.targetColor = Color.white;
state.targetScale = 1f;
// 平滑过渡
SmoothTransition(state, particle, visualTransitionDuration);
// 应用样式
if (particle.textMesh != null)
{
particle.textMesh.fontStyle = FontStyles.Normal;
}
// 清除光晕
SetParticleGlow(particle, Color.clear, 0f);
// 停止抖动
if (state.jitterTween != null)
{
state.jitterTween.Kill();
state.jitterTween = null;
}
particle.transform.position = state.basePosition;
return 0f;
}
/// <summary>
/// 中性状态:噪音/普通内容(参考 HTML)
/// </summary>
private void ApplyNeutralState(TextParticle particle, GridCell cellData)
{
Vector2Int pos = new Vector2Int(cellData.row, cellData.col);
if (!particleStates.ContainsKey(pos))
{
particleStates[pos] = new ParticleVisualState
{
basePosition = particle.transform.position,
currentAlpha = particle.alpha,
targetAlpha = particle.alpha,
currentColor = particle.textMesh.color,
targetColor = particle.textMesh.color,
currentScale = 1f,
targetScale = 1f,
isShowingWord = true
};
}
ParticleVisualState state = particleStates[pos];
// 确保显示词语
if (particle.currentChar != cellData.word)
{
particle.ForceSetCharacter(cellData.word);
state.isShowingWord = true;
}
// 中性颜色:灰色,淡化显示
Color neutralColor = new Color(0.4f, 0.5f, 0.4f, 1f);
state.targetAlpha = 0.3f;
state.targetColor = neutralColor;
state.targetScale = 1f;
// 平滑过渡
SmoothTransition(state, particle, visualTransitionDuration);
// 应用样式
if (particle.textMesh != null)
{
particle.textMesh.fontStyle = FontStyles.Normal;
}
// 清除光晕
SetParticleGlow(particle, Color.clear, 0f);
// 停止抖动
if (state.jitterTween != null)
{
state.jitterTween.Kill();
state.jitterTween = null;
}
particle.transform.position = state.basePosition;
}
/// <summary>
/// 平滑过渡到目标视觉状态
/// </summary>
private void SmoothTransition(ParticleVisualState state, TextParticle particle, float duration)
{
// Alpha 过渡
if (Mathf.Abs(state.currentAlpha - state.targetAlpha) > 0.01f)
{
if (state.alphaTween != null && state.alphaTween.IsActive())
{
state.alphaTween.Kill();
}
state.alphaTween = DOTween.To(() => state.currentAlpha, x => {
state.currentAlpha = x;
particle.alpha = x;
}, state.targetAlpha, duration).SetEase(Ease.OutQuad);
}
// Color 过渡
if (ColorDistance(state.currentColor, state.targetColor) > 0.01f)
{
if (state.colorTween != null && state.colorTween.IsActive())
{
state.colorTween.Kill();
}
state.colorTween = DOTween.To(() => state.currentColor, x => {
state.currentColor = x;
if (particle.textMesh != null)
{
Color finalColor = x;
finalColor.a = state.currentAlpha; // 保持alpha同步
particle.textMesh.color = finalColor;
}
}, state.targetColor, duration).SetEase(Ease.OutQuad);
}
// Scale 过渡(通过transform.localScale
if (Mathf.Abs(state.currentScale - state.targetScale) > 0.01f)
{
if (state.scaleTween != null && state.scaleTween.IsActive())
{
state.scaleTween.Kill();
}
Vector3 targetScale = Vector3.one * state.targetScale;
state.scaleTween = particle.transform.DOScale(targetScale, duration).SetEase(Ease.OutQuad);
state.currentScale = state.targetScale;
}
}
/// <summary>
/// 计算两个颜色的距离(用于判断是否需要过渡)
/// </summary>
private float ColorDistance(Color a, Color b)
{
return Mathf.Abs(a.r - b.r) + Mathf.Abs(a.g - b.g) + Mathf.Abs(a.b - b.b);
}
/// <summary>
/// 设置粒子光晕效果(平滑过渡)
/// </summary>
private void SetParticleGlow(TextParticle particle, Color color, float alpha)
{
if (particle.glowSprite != null)
{
Color glowColor = color;
glowColor.a = alpha;
// 使用DOTween平滑过渡光晕
particle.glowSprite.DOColor(glowColor, visualTransitionDuration).SetEase(Ease.OutQuad);
}
}
/// <summary>
/// 更新信号强度显示
/// </summary>
private void UpdateSignalStrengthDisplay(float maxSignal)
{
if (signalStrengthText != null)
{
signalStrengthText.text = $"信号强度: {Mathf.RoundToInt(maxSignal)}%";
}
}
private void OnVerifyClicked()
{
if (!isFilteringActive || currentLevelData == null) return;
// 检查解码器位置是否正确
bool isPositionCorrect = (currentDecoderGridPos == currentLevelData.correctDecoderPosition);
// 检查频率是否正确(匹配答案情绪)
EmotionConfig answerEmotion = GetEmotionConfig(currentLevelData.answerEmotion);
float frequencyDistance = Mathf.Abs(currentFrequency - answerEmotion.frequency);
bool isFrequencyCorrect = frequencyDistance <= currentLevelData.frequencyTolerance;
// 验证答案
bool isCorrect = isPositionCorrect && isFrequencyCorrect;
if (isCorrect)
{
Debug.Log($"[AnalysisModeManager] 验证成功!位置: {currentDecoderGridPos}, 频率: {currentFrequency:F1} Hz");
// 显示成功反馈
ShowDecoderFeedback(true);
// 延迟后触发完成回调
StartCoroutine(DelayedComplete(true));
}
else
{
Debug.Log($"[AnalysisModeManager] 验证失败。位置正确: {isPositionCorrect}, 频率正确: {isFrequencyCorrect}");
// 显示失败反馈
ShowDecoderFeedback(false);
}
}
/// <summary>
/// 显示解码器验证反馈
/// </summary>
private void ShowDecoderFeedback(bool isCorrect)
{
if (decoderGridDrawerObj == null) return;
// 改变解码器边框颜色
Color feedbackColor = isCorrect ? Color.green : Color.red;
Color originalColor = new Color(0f, 1f, 1f, 0.8f); // Cyan 原始颜色
// 找到所有网格线并改变颜色
Transform linesContainer = decoderGridDrawerObj.transform.Find("Lines");
if (linesContainer != null)
{
Line[] lines = linesContainer.GetComponentsInChildren<Line>();
foreach (Line line in lines)
{
Color startColor = line.Color;
// 使用DOTween.To来动画化颜色变化
DOTween.To(() => startColor, x => line.Color = x, feedbackColor, 0.3f)
.SetEase(Ease.OutQuad);
if (!isCorrect)
{
// 失败时闪烁后恢复
DOTween.To(() => feedbackColor, x => line.Color = x, originalColor, 0.3f)
.SetDelay(0.6f)
.SetEase(Ease.OutQuad);
}
}
}
}
/// <summary>
/// 延迟完成(用于播放动画)
/// </summary>
private System.Collections.IEnumerator DelayedComplete(bool success)
{
yield return new WaitForSeconds(1f);
OnAnalysisComplete(success);
// 关闭分析模式
if (analysisPanel != null)
{
analysisPanel.gameObject.SetActive(false);
}
}
private void OnAnalysisComplete(bool success)
{
Debug.Log($"[AnalysisModeManager] OnAnalysisComplete 被调用,结果: {(success ? "成功" : "失败")}");
Debug.Log($"[AnalysisModeManager] completionNodeName: {(string.IsNullOrEmpty(completionNodeName) ? "" : completionNodeName)}");
Debug.Log($"[AnalysisModeManager] currentLevelData: {(currentLevelData == null ? "null" : currentLevelData.levelName)}");
// 通过 Yarn 对话系统反馈结果
if (success)
{
// 成功节点:优先使用 completionNodeName,其次使用配置中的节点
string successNode = !string.IsNullOrEmpty(completionNodeName)
? completionNodeName
: (currentLevelData != null && !string.IsNullOrEmpty(currentLevelData.successNodeName))
? currentLevelData.successNodeName
: "";
Debug.Log($"[AnalysisModeManager] 成功节点选择: {(string.IsNullOrEmpty(successNode) ? "未找到节点" : successNode)}");
if (!string.IsNullOrEmpty(successNode))
{
Debug.Log($"[AnalysisModeManager] 准备启动对话节点: {successNode}");
if (DialogController.Instance == null)
{
Debug.LogError("[AnalysisModeManager] DialogController.Instance 为 null,无法启动对话节点!");
}
else
{
DialogController.Instance.StartDialogNode(successNode);
Debug.Log($"[AnalysisModeManager] 已调用 StartDialogNode: {successNode}");
}
}
else
{
Debug.LogWarning("[AnalysisModeManager] 成功时未找到任何对话节点!");
}
}
else
{
// 失败节点
string failNode = !string.IsNullOrEmpty(completionNodeName)
? completionNodeName + "_Fail"
: (currentLevelData != null && !string.IsNullOrEmpty(currentLevelData.failNodeName))
? currentLevelData.failNodeName
: "";
Debug.Log($"[AnalysisModeManager] 失败节点选择: {(string.IsNullOrEmpty(failNode) ? "未找到节点" : failNode)}");
if (!string.IsNullOrEmpty(failNode))
{
Debug.Log($"[AnalysisModeManager] 准备启动对话节点: {failNode}");
if (DialogController.Instance == null)
{
Debug.LogError("[AnalysisModeManager] DialogController.Instance 为 null,无法启动对话节点!");
}
else
{
DialogController.Instance.StartDialogNode(failNode);
Debug.Log($"[AnalysisModeManager] 已调用 StartDialogNode: {failNode}");
}
}
else
{
Debug.LogWarning("[AnalysisModeManager] 失败时未找到任何对话节点!");
}
}
}
private EmotionConfig GetEmotionConfig(EmotionType type)
{
switch (type)
{
case EmotionType.Joy: return EmotionConfig.Joy;
case EmotionType.Sadness: return EmotionConfig.Sadness;
case EmotionType.Anger: return EmotionConfig.Anger;
case EmotionType.Fear: return EmotionConfig.Fear;
default: return EmotionConfig.Neutral;
}
}
}
}