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}");
// }
}
}