using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System.Collections.Generic;
using System.Linq;
using DG.Tweening;
using Shapes;
using AibisDream.FixSystem;
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
{
[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);
SetupUI();
}
///
/// 进入分析模式(由状态机调用)
///
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;
}
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;
}
// 停止抖动
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 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;
}
// 停止抖动
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;
}
// 停止抖动
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;
}
// 停止抖动
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 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;
}
}
}
}