using UnityEngine; using UnityEngine.UI; using TMPro; using System.Collections.Generic; using System.Linq; using DG.Tweening; 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 string cover; // 笑话掩盖词 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 string cover; // 笑话掩盖词 } /// /// 分析模式管理器 /// public class AnalysisModeManager : MonoBehaviour { [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; // [SerializeField] private TMP_Text logText; // [SerializeField] private ScrollRect logScrollRect; [Header("网格配置")] [SerializeField] private RectTransform gridContainer; // 可选,用于定位网格中心 // [SerializeField] private GameObject gridCellPrefab; // 不需要了,直接使用粒子 [SerializeField] private int gridWidth = 20; [SerializeField] private int gridHeight = 20; [SerializeField] private float cellSize = 0.4f; // Unity 单位(与 LanguageParticleManager 匹配) [SerializeField] private float cellGap = 0.05f; // Unity 单位 [SerializeField] private float cellFontSize = 2.5f; // 文字大小(与 LanguageParticleManager 匹配) [SerializeField] private Vector3 gridCenterOffset = Vector3.zero; // 网格中心偏移量 [Header("解码器条纹")] [SerializeField] private RectTransform decoderStrip; // [SerializeField] private GameObject stripHolePrefab; // 暂时不用,条纹作为独立 prefab [SerializeField] private int stripWidth = 5; // 条纹宽度(列数) [SerializeField] private int stripHeight = 3; // 条纹高度(行数) [Header("字体配置")] [SerializeField] private TMP_FontAsset chineseFontAsset; // 中文字体资源 [Header("动画配置")] [SerializeField] private float panelSlideDuration = 0.5f; [SerializeField] private float dataLoadDuration = 1.5f; [SerializeField] private Ease slideEase = Ease.OutCubic; [Header("焦虑短句配置")] [SerializeField] private List anxietyPhrases = new List { "临检要来了大家都很紧张只有销售员说终于可以检查业绩了", "不合格英里被骂了因为销售业绩太差了", "我担心业绩会下滑结果真的下滑了我的担心是对的", "销售员总是混淆客户和老板因为都要讨好", "一个销售员走进酒吧问两张桌子要不要买保险" }; [Header("目标配置")] [SerializeField] private string targetSentence = "[临检]要来了,不合格[英里]被骂了,这让我很[担心]"; // 解析目标:只有一个正确答案 private List analysisTargets = new List(); // 状态 private bool isAnalysisModeActive = false; private bool isFilteringActive = false; private float currentFrequency = 50f; private GridCell[,] gridData; private Dictionary gridParticles = new Dictionary(); // 使用粒子代替 UI // private List stripHolePositions = new List(); // 暂时不用,条纹作为独立 prefab 配置 private bool isInitialized = false; private string completionNodeName = ""; private void Awake() { SetupUI(); SetupTargets(); } private void SetupUI() { if (analysisPanel != null) { analysisPanel.gameObject.SetActive(false); } if (initializeButton != null) { initializeButton.onClick.AddListener(OnInitializeClicked); // 初始化按钮现在作为"重置网格"使用,网格会自动创建 } 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; } if (decoderStrip != null) { decoderStrip.gameObject.SetActive(false); // SetupDecoderStripDragging(); // 暂时注释,条纹作为独立 prefab 配置 } } private void SetupTargets() { // 清空目标列表 analysisTargets.Clear(); // 创建正确答案目标 var correctTarget = new AnalysisTarget { name = "Fear Solution 1", emotion = EmotionType.Fear, stripPosition = new Vector2Int(3, 5), // 条纹正确位置 isCorrectAnswer = true }; // 添加目标词:临检、英里、担心(相对于条纹的孔洞位置) correctTarget.words.Add(new TargetWord { position = new Vector2Int(3, 5), // 绝对位置 = 条纹位置 + 孔洞相对位置 word = "临检", cover = "检查" }); correctTarget.words.Add(new TargetWord { position = new Vector2Int(4, 6), word = "英里", cover = "背后" }); correctTarget.words.Add(new TargetWord { position = new Vector2Int(5, 9), word = "担心", cover = "对的" }); analysisTargets.Add(correctTarget); // 可以添加更多诱饵目标(与正确答案接近但不完全匹配) // 这里暂时只有一个正确答案 } /// /// 启动分析模式(由 ExpressionManager 调用) /// public void StartAnalysisMode(List phrases = null, string completionNode = null) { if (isAnalysisModeActive) return; // 更新配置 if (phrases != null && phrases.Count > 0) { anxietyPhrases = phrases; } completionNodeName = completionNode ?? ""; // 显示分析控制面板 isAnalysisModeActive = true; if (analysisPanel != null) { analysisPanel.gameObject.SetActive(true); } // 自动初始化网格(不需要等待用户点击 Initialize) if (!isInitialized) { InitializeGrid(); } // Log("> 分析模式已启动"); // Log("> 点击 [初始化] 导入数据..."); } /// /// 关闭分析模式 /// public void CloseAnalysisMode() { if (!isAnalysisModeActive) return; // 滑出 UI 面板 if (analysisPanel != null) { Vector2 endPos = new Vector2(Screen.width, 0); analysisPanel.DOAnchorPos(endPos, panelSlideDuration) .SetEase(slideEase) .OnComplete(() => { analysisPanel.gameObject.SetActive(false); isAnalysisModeActive = false; }); } else { isAnalysisModeActive = false; } // 重置状态 isFilteringActive = false; isInitialized = false; currentFrequency = 50f; ClearGrid(); if (decoderStrip != null) { decoderStrip.gameObject.SetActive(false); } } private void OnInitializeClicked() { // 现在网格会在 StartAnalysisMode 时自动初始化 // 这个按钮可以用来重新初始化网格(重置) if (isInitialized) { // 重新初始化(重置网格) isInitialized = false; ClearGrid(); } // Log("> 开始导入焦虑词语数据..."); InitializeGrid(); } private void InitializeGrid() { // 创建网格数据 gridData = new GridCell[gridHeight, gridWidth]; // 将焦虑短句分割成2字符片段 List allFragments = new List(); foreach (var phrase in anxietyPhrases) { var fragments = SplitIntoTwoCharChunks(phrase); allFragments.AddRange(fragments); } // 如果片段不够填满网格,重复使用 int totalCells = gridWidth * gridHeight; while (allFragments.Count < totalCells) { allFragments.AddRange(allFragments.Take(totalCells - allFragments.Count)); } // 填充网格数据 int fragmentIndex = 0; for (int row = 0; row < gridHeight; row++) { for (int col = 0; col < gridWidth; col++) { GridCell cell = new GridCell { row = row, col = col, word = allFragments[fragmentIndex % allFragments.Count], cover = allFragments[fragmentIndex % allFragments.Count], emotion = EmotionType.Neutral, isTarget = false }; // 检查是否是目标位置 foreach (var target in analysisTargets) { foreach (var targetWord in target.words) { if (targetWord.position.x == row && targetWord.position.y == col) { cell.word = targetWord.word; cell.cover = targetWord.cover; cell.emotion = target.emotion; cell.isTarget = true; break; } } if (cell.isTarget) break; } // 为非目标单元格随机分配一些情绪(作为诱饵) if (!cell.isTarget && Random.value > 0.88f) { EmotionType[] emotions = { EmotionType.Fear, EmotionType.Sadness, EmotionType.Joy, EmotionType.Anger }; cell.emotion = emotions[Random.Range(0, emotions.Length)]; } gridData[row, col] = cell; fragmentIndex++; } } // 创建网格 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; // 计算起始位置(左上角) Vector3 gridStartPos = gridCenter - new Vector3(totalWidth / 2f, totalHeight / 2f, 0f); // 随机顺序创建单元格(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); // Log("> 数据导入完成"); // Log($"> 共 {gridWidth}x{gridHeight} 个数据节点"); // Log("> 点击 [启动过滤分析] 开始..."); isInitialized = true; if (startFilterButton != null) { startFilterButton.interactable = true; } } /// /// 创建网格粒子(直接使用 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 = false; particle.velocity = Vector2.zero; // 重要:设置透明度,否则粒子不可见! particle.alpha = 1f; particle.baseAlpha = 1f; // 设置文字内容 if (gridData != null) { GridCell cellData = gridData[row, col]; particle.ForceSetCharacter(cellData.cover); // 初始显示掩盖词 // 设置字体大小和颜色 if (particle.textMesh != null) { particle.textMesh.fontSize = cellFontSize; particle.textMesh.color = Color.white; } } gridParticles[new Vector2Int(row, col)] = particle; } private void ClearGrid() { foreach (var particle in gridParticles.Values) { if (particle != null) { Destroy(particle.gameObject); } } gridParticles.Clear(); } 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; // Log("> 过滤分析已启动"); // Log("> 解码器条纹已加载"); // Log("> 调整情绪频率以隔离信号模式..."); // 显示解码器条纹(暂时注释,作为独立 prefab 后续配置) // if (decoderStrip != null) // { // decoderStrip.gameObject.SetActive(true); // // // 初始位置设置到正确答案位置(玩家可以拖动) // var correctTarget = analysisTargets.FirstOrDefault(t => t.isCorrectAnswer); // if (correctTarget != null) // { // PositionDecoderStrip(correctTarget.stripPosition.x, correctTarget.stripPosition.y); // } // // // 淡入动画 // CanvasGroup canvasGroup = decoderStrip.GetComponent(); // if (canvasGroup == null) // { // canvasGroup = decoderStrip.gameObject.AddComponent(); // } // canvasGroup.alpha = 0f; // canvasGroup.DOFade(1f, 0.8f); // } // 启用滑动条和验证按钮 if (emotionSlider != null) { emotionSlider.interactable = true; } if (verifyButton != null) { verifyButton.interactable = true; } if (startFilterButton != null) { startFilterButton.interactable = false; } // 应用初始过滤效果 UpdateGridVisualsWithFilter(); } private void PositionDecoderStrip(int row, int col) { if (decoderStrip == null || gridContainer == null) return; // 使用解码器组件设置位置 AnalysisDecoderStrip stripComponent = decoderStrip.GetComponent(); if (stripComponent != null) { stripComponent.SetGridPosition(row, col, cellSize, cellGap); } else { // 备用设置方式 float xPos = col * (cellSize + cellGap); float yPos = -row * (cellSize + cellGap); decoderStrip.anchoredPosition = new Vector2(xPos, yPos); } // 设置条纹大小 float stripWidth = this.stripWidth * cellSize + (this.stripWidth - 1) * cellGap; float stripHeight = this.stripHeight * cellSize + (this.stripHeight - 1) * cellGap; decoderStrip.sizeDelta = new Vector2(stripWidth, stripHeight); } // 解码器条纹拖拽逻辑 - 暂时注释,条纹作为独立 prefab 配置 // private void SetupDecoderStripDragging() // { // if (decoderStrip == null) return; // // // 添加拖拽组件 // var draggable = decoderStrip.gameObject.AddComponent(); // // // 这里需要实现拖拽逻辑,但由于 EventTrigger 比较复杂, // // 暂时先让条纹固定在初始位置,玩家可以通过调整频率来验证 // } 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 UpdateGridVisualsWithFilter() { if (gridData == null) return; float maxSignal = 0f; for (int row = 0; row < gridHeight; row++) { for (int col = 0; col < gridWidth; col++) { GridCell cellData = gridData[row, col]; Vector2Int pos = new Vector2Int(row, col); if (!gridParticles.ContainsKey(pos)) continue; TextParticle particle = gridParticles[pos]; if (particle == null || particle.textMesh == null) continue; // 计算与当前频率的距离 EmotionConfig emotion = GetEmotionConfig(cellData.emotion); float distance = 100f; if (emotion.type != EmotionType.Neutral) { distance = Mathf.Abs(currentFrequency - emotion.frequency); } // 根据距离更新显示 if (cellData.emotion != EmotionType.Neutral) { bool isTargetWord = cellData.word != cellData.cover; if (distance < 5f) { // 锁定:显示真实词语,高亮显示 particle.ForceSetCharacter(cellData.word); particle.textMesh.color = emotion.color; particle.textMesh.fontStyle = FontStyles.Bold; particle.alpha = 1f; // 调整光晕效果 if (particle.glowSprite != null) { Color glowColor = emotion.color; glowColor.a = 0.5f; particle.glowSprite.color = glowColor; } maxSignal = Mathf.Max(maxSignal, 100f - distance * 20f); } else if (distance < 20f) { // 闪烁区:在真实词和掩盖词之间闪烁 if (isTargetWord) { float glitchChance = 1f - (distance / 20f); if (Random.value < glitchChance) { particle.ForceSetCharacter(cellData.word); particle.textMesh.color = emotion.color; } else { particle.ForceSetCharacter(cellData.cover); particle.textMesh.color = Color.white; } } else { particle.ForceSetCharacter(cellData.word); particle.textMesh.color = emotion.color; } particle.textMesh.fontStyle = FontStyles.Normal; particle.alpha = 0.9f; if (particle.glowSprite != null) { Color glowColor = emotion.color; glowColor.a = 0.3f; particle.glowSprite.color = glowColor; } } else if (distance < 35f) { // 淡出区:显示掩盖词,逐渐淡化 particle.ForceSetCharacter(isTargetWord ? cellData.cover : cellData.word); float alpha = 0.6f - ((distance - 20f) / 30f); Color color = Color.white; color.a = alpha; particle.textMesh.color = color; particle.textMesh.fontStyle = FontStyles.Normal; particle.alpha = alpha; if (particle.glowSprite != null) { particle.glowSprite.color = new Color(1f, 1f, 1f, 0f); } } else { // 消失区:几乎不可见 particle.ForceSetCharacter(isTargetWord ? cellData.cover : cellData.word); Color color = Color.white; color.a = 0.05f; particle.textMesh.color = color; particle.textMesh.fontStyle = FontStyles.Normal; particle.alpha = 0.05f; if (particle.glowSprite != null) { particle.glowSprite.color = new Color(0f, 0f, 0f, 0f); } } } else { // 中性/噪音:始终淡化显示 particle.ForceSetCharacter(cellData.word); Color color = new Color(0.27f, 0.33f, 0.27f, 0.3f); particle.textMesh.color = color; particle.textMesh.fontStyle = FontStyles.Normal; particle.alpha = 0.3f; if (particle.glowSprite != null) { particle.glowSprite.color = new Color(0f, 0f, 0f, 0f); } } } } // 更新信号强度显示 if (signalStrengthText != null) { signalStrengthText.text = $"信号强度: {Mathf.RoundToInt(maxSignal)}%"; } } private void OnVerifyClicked() { if (!isFilteringActive) return; // Log($"> 正在扫描扇区..."); // Log($"> 当前频率: {currentFrequency:F1} Hz"); // 获取解码器条纹当前位置 Vector2Int stripPos = Vector2Int.zero; if (decoderStrip != null) { // 使用解码器组件获取位置 AnalysisDecoderStrip stripComponent = decoderStrip.GetComponent(); if (stripComponent != null) { stripPos = stripComponent.GetGridPosition(cellSize, cellGap); } else { // 备用计算方式 Vector2 anchoredPos = decoderStrip.anchoredPosition; int col = Mathf.RoundToInt(anchoredPos.x / (cellSize + cellGap)); int row = Mathf.RoundToInt(-anchoredPos.y / (cellSize + cellGap)); stripPos = new Vector2Int(row, col); } } // Log($"> 条纹位置: [{stripPos.x}, {stripPos.y}]"); // 检查是否匹配任何目标 bool foundMatch = false; AnalysisTarget matchedTarget = null; foreach (var target in analysisTargets) { if (target.stripPosition == stripPos) { EmotionConfig emotion = GetEmotionConfig(target.emotion); float distance = Mathf.Abs(currentFrequency - emotion.frequency); if (distance < 8f) { // 匹配成功 foundMatch = true; matchedTarget = target; break; } else { // Log("> 发现模式但信号微弱,请调整频率"); // Log("> 似乎要说的不是这个..."); return; } } } if (foundMatch && matchedTarget != null) { // 成功匹配 // Log($"> 解密成功!"); // Log($"> 完整句子: {targetSentence}"); // Log("> 寻找完成"); // 触发成功回调 OnAnalysisComplete(true); } else { // 未找到匹配 // Log("> 未检测到数据对齐"); // Log("> 似乎要说的不是这个..."); // 触发失败回调 OnAnalysisComplete(false); } } private void OnAnalysisComplete(bool success) { // 通过 Yarn 对话系统反馈结果 if (!string.IsNullOrEmpty(completionNodeName)) { if (success) { // 成功节点 DialogController.Instance?.StartDialogNode(completionNodeName + "_Success"); } else { // 失败节点 DialogController.Instance?.StartDialogNode(completionNodeName + "_Fail"); } } } 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; } } // private void Log(string message) // { // if (logText != null) // { // logText.text += message + "\n"; // // 自动滚动到底部 // if (logScrollRect != null) // { // Canvas.ForceUpdateCanvases(); // logScrollRect.verticalNormalizedPosition = 0f; // } // } // Debug.Log($"[AnalysisMode] {message}"); // } } }