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 { /// /// 情绪类型 /// public enum EmotionType { Joy, // 快乐 - 15Hz Sadness, // 悲伤 - 40Hz Anger, // 愤怒 - 65Hz Fear, // 恐惧 - 90Hz Neutral // 中性/噪音 } /// /// 情绪配置 /// [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" }; } /// /// 网格单元格数据 /// [System.Serializable] public class GridCell { public string word; // 词语内容 public EmotionType emotion; public bool isTarget; // 是否是目标词 public int row; public int col; } /// /// 解析目标配置 /// [System.Serializable] public class AnalysisTarget { public string name; public EmotionType emotion; public List words = new List(); public Vector2Int stripPosition; // 解码器条纹正确位置(行,列) public bool isCorrectAnswer; // 是否是正确答案 } /// /// 目标词配置 /// [System.Serializable] public class TargetWord { public Vector2Int position; // 在网格中的位置(行,列) public string word; // 词语内容 } /// /// 粒子视觉状态(用于平滑过渡) /// 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; } /// /// 分析模式管理器 /// 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 analysisTargets = new List(); 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 gridParticles = new Dictionary(); private bool isInitialized = false; private string completionNodeName = ""; // 平滑过渡状态存储 private Dictionary particleStates = new Dictionary(); // 闪烁计时器(用于平滑闪烁) private Dictionary glitchTimers = new Dictionary(); // Shapes 格子绘制器 private GameObject mainGridDrawerObj; private GameObject decoderGridDrawerObj; private Vector3 gridStartPos; // 网格起始位置(左上角) // 解码器相关 private Dictionary decoderParticles = new Dictionary(); 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(); if (virtualCam != null) { CameraKit.Instance.RegisterCamera(CameraEnum.AnalysisMode, virtualCam); } SetupUI(); } private void OnDestroy() { CameraKit.Instance?.UnRegisterCamera(CameraEnum.AnalysisMode); CameraKit.Instance?.UnRegisterCamera(CameraEnum.AnalysisModeDeep); } /// /// 进入分析模式(由状态机调用) /// public void OnEnter() { // 开启分析模式视图 // if (!isAnalysisModeActive) // { // OpenView(); // } } /// /// 退出分析模式(由状态机调用) /// 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; } } /// /// 加载关卡数据(数据驱动) /// 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}"); } /// /// 启动分析模式(独立系统入口) /// /// 关卡配置数据 /// 完成回调节点(可选,会覆盖配置中的节点) 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(); } } /// /// 关闭分析模式 /// 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 cellPositions = new List(); 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; } } /// /// 创建主矩阵 Shapes 格子绘制器 /// 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; // 添加 RectTransform(Canvas 下通常需要,虽然 Shapes 主要用 Transform) RectTransform rect = mainGridDrawerObj.AddComponent(); 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.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; } /// /// 创建解码器 /// private void CreateDecoder() { if (currentLevelData == null || sharedCanvas == null) return; // 清理旧的解码器 ClearDecoder(); // 初始化解码器位置(网格坐标,默认在中心附近) currentDecoderGridPos = new Vector2Int( gridHeight / 2 - stripHeight / 2, gridWidth / 2 - stripWidth / 2 ); // 创建解码器网格绘制器 CreateDecoderGridDrawer(); // 创建解码器文字粒子 CreateDecoderParticles(); // 更新解码器位置 UpdateDecoderPosition(); } /// /// 创建解码器网格绘制器(类似MainGridDrawer) /// 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(); 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(); } /// /// 创建解码器网格线(与主网格不同的颜色和渲染顺序) /// private void CreateDecoderGridLine(Transform parent, Vector3 start, Vector3 end) { GameObject lineObj = new GameObject("DecoderGridLine"); lineObj.transform.SetParent(parent, false); Line line = lineObj.AddComponent(); 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; } /// /// 添加解码器拖拽处理器 /// private void AddDecoderDragHandler() { if (decoderGridDrawerObj == null) return; // 创建一个透明的Image作为拖拽区域 GameObject dragAreaObj = new GameObject("DragArea"); dragAreaObj.transform.SetParent(decoderGridDrawerObj.transform, false); RectTransform dragRect = dragAreaObj.AddComponent(); 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(); dragImage.color = new Color(0f, 0f, 0f, 0.1f); // 极淡的半透明背景,主要用于拖拽响应 dragImage.raycastTarget = true; // 添加 EventTrigger 组件用于处理鼠标事件 UnityEngine.EventSystems.EventTrigger eventTrigger = dragAreaObj.AddComponent(); // 开始拖拽 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); } /// /// 创建解码器文字粒子 /// private void CreateDecoderParticles() { if (currentLevelData == null || sharedCanvas == null) return; // 解析解码器文字 string decoderText = currentLevelData.decoderText; List words = SplitIntoTwoCharChunks(decoderText); int wordIndex = 0; // 创建HashSet用于快速查找孔洞 HashSet holes = new HashSet(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++; } } } } /// /// 创建单个解码器粒子 /// 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.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(); 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; } /// /// 创建解码器单元格背景(遮挡主网格文字) /// 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(); // 背景大小应该填充整个格子区域(包括间隙),确保完全遮挡下方文字 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; } /// /// 更新解码器位置(对齐到网格) /// 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; } /// /// 开始拖拽解码器 /// 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; } } } /// /// 拖拽解码器中 /// 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(); } } } /// /// 结束拖拽解码器 /// private void OnDecoderEndDrag(UnityEngine.EventSystems.PointerEventData eventData) { isDraggingDecoder = false; // 确保最终位置对齐到网格 UpdateDecoderPosition(); } /// /// 清理解码器 /// private void ClearDecoder() { // 清理解码器粒子 foreach (var particle in decoderParticles.Values) { if (particle != null) { Destroy(particle.gameObject); } } decoderParticles.Clear(); // 清理解码器绘制器 if (decoderGridDrawerObj != null) { Destroy(decoderGridDrawerObj); decoderGridDrawerObj = null; } } /// /// 创建网格粒子(直接使用 TextParticle,与 LanguageParticleManager 一致) /// 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.SetParent(sharedCanvas.transform, false); rectTransform.position = position; rectTransform.localScale = Vector3.one; rectTransform.sizeDelta = Vector2.zero; // 添加 TextParticle 组件 TextParticle particle = particleObj.AddComponent(); 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 SplitIntoTwoCharChunks(string text) { List chunks = new List(); 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); } /// /// 更新单个单元格的视觉效果 /// 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); } } /// /// 计算与当前频率的距离 /// private float CalculateFrequencyDistance(EmotionConfig emotion) { if (emotion.type == EmotionType.Neutral) { return 100f; } return Mathf.Abs(currentFrequency - emotion.frequency); } /// /// 应用情绪相关的视觉状态(参考 HTML 原型的视觉效果) /// 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); } } /// /// 锁定状态:频率匹配,高亮显示(参考 HTML) /// 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; } /// /// 闪烁状态:颜色和光晕闪烁(参考 HTML) /// 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; } /// /// 淡出状态:逐渐淡化(参考 HTML) /// 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; } /// /// 隐藏状态:几乎不可见(参考 HTML) /// 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; } /// /// 中性状态:噪音/普通内容(参考 HTML) /// 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; } /// /// 平滑过渡到目标视觉状态 /// 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; } } /// /// 计算两个颜色的距离(用于判断是否需要过渡) /// 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); } /// /// 设置粒子光晕效果(平滑过渡) /// 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); } } /// /// 更新信号强度显示 /// 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); } } /// /// 显示解码器验证反馈 /// 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(); 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); } } } } /// /// 延迟完成(用于播放动画) /// 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; } } } }