using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using DG.Tweening;
using Shapes;
using TMPro;
using UnityEngine.UI;
namespace AibisDream.MiniGame.Language
{
///
/// 粒子排布区域形状
///
public enum LayoutShape
{
Rectangle, // 矩形
Circle // 圆形
}
///
/// 游戏完成阶段
///
public enum CompletionPhase
{
None, // 未完成
Completed, // 已完成但尚未进入聚焦流程
Focusing, // 正在聚焦放大并收敛
Finished // 全流程完成
}
///
/// 语言粒子系统主管理器
///
public class LanguageParticleManager : MonoBehaviour
{
[Header("Canvas设置")]
[SerializeField] private Canvas worldCanvas;
[SerializeField] private float canvasSize = 10f;
[SerializeField] private LayoutShape layoutShape = LayoutShape.Rectangle;
[Tooltip("圆形布局时的半径(留空则自动使用 canvasSize/2 - textMargin)")]
[SerializeField] private float circleRadiusOverride = -1f;
[Header("字体设置")]
[SerializeField] private TMP_FontAsset chineseFontAsset;
[Header("粒子预制体")]
[SerializeField] private GameObject floatingParticlePrefab;
[SerializeField] private GameObject candidateParticlePrefab;
[Header("粒子数量")]
[SerializeField] private int floatingCount = 100;
[SerializeField] private int candidateCount = 50;
[SerializeField] private int redParticleCount = 8;
[Header("游戏参数")]
[SerializeField] private float connectionDistance = 2f; // Unity单位
[SerializeField] private float textMargin = 1f;
[Header("游戏配置")]
[SerializeField] private List anxietyPhrases = new List(); // 焦虑短句配置
[SerializeField] private string targetSentence = "这是一个测试示例"; // 目标句子
[SerializeField] private string completionDialogNode = ""; // 完成时触发的对话节点
[Header("波形参数")]
[SerializeField] private float waveformAmplitude = 0.3f;
[SerializeField] private float interactionRadius = 0.5f;
[Header("鼠标交互参数")]
[SerializeField] private float mouseInteractionRadius = 1.5f;
[SerializeField] private float repulsionForce = 2f;
[SerializeField] private float attractionForce = 1.5f;
[Header("颜色配置(用于粒子/字的颜色)")]
[SerializeField] private Color targetParticleColor = new Color(1f, 0.39f, 0.39f, 1f); // 目标粒子颜色(红)
[SerializeField] private Color nonTargetParticleColor = new Color(0.2f, 0.78f, 1f, 1f); // 非目标粒子颜色(蓝)
[SerializeField] private Color floatingTextColor = new Color(0.7f, 0.78f, 1f, 1f); // 背景浮动文字颜色
[SerializeField] private Color targetCalmColor = new Color(0.78f, 0.59f, 0.59f, 1f); // 目标粒子平静后颜色
[Header("连接线颜色(用于连线的颜色,与上面可分开调节)")]
[SerializeField] private Color targetConnectionColor = new Color(1f, 0.39f, 0.39f, 1f); // 目标连线颜色
[SerializeField] private Color nonTargetConnectionColor = new Color(0.39f, 0.78f, 1f, 0.7f);// 非目标连线颜色
[Header("文字样式配置")]
[SerializeField] private float targetParticleFontSize = 4.5f; // 目标粒子字体大小
[SerializeField] private float nonTargetParticleFontSize = 3.7f; // 非目标粒子字体大小
[SerializeField] private float floatingTextFontSize = 3f; // 背景浮动文字大小
[SerializeField] private bool targetParticleBold = true; // 目标粒子是否加粗
[SerializeField] private bool nonTargetParticleBold = false; // 非目标粒子是否加粗
[Header("连接线粗细")]
[SerializeField] private float targetConnectionThickness = 0.04f; // 目标连线粗细
[SerializeField] private float nonTargetConnectionThickness = 0.02f; // 非目标连线粗细
[Header("Bloom 发光效果")]
[SerializeField] private bool enableBloomGlow = true; // 是否启用 Bloom 发光
[SerializeField, Range(1f, 5f)] private float textGlowIntensity = 1.8f; // 文字发光强度
[SerializeField, Range(1f, 5f)] private float targetTextGlowIntensity = 2.5f; // 目标粒子文字发光强度
[SerializeField, Range(1f, 5f)] private float lineGlowIntensity = 2f; // 连接线发光强度
[SerializeField, Range(1f, 5f)] private float targetLineGlowIntensity = 2.5f; // 目标连接线发光强度
[SerializeField, Range(0f, 1f)] private float glowDilate = 0.3f; // 发光扩散
[SerializeField, Range(0f, 1f)] private float glowSoftness = 0.5f; // 发光柔和度
[Header("目标 UI")]
[SerializeField] private TMP_Text integrationProgressText;
[SerializeField] private TMP_Text interferenceCountText;
[Header("完成阶段 UI")]
[SerializeField] private Button focusSequenceButton;
[SerializeField] private float focusZoomScale = 1.6f;
[SerializeField] private float focusMoveDuration = 1.2f;
[SerializeField] private float focusFadeDuration = 1f;
[SerializeField] private float probabilityDuration = 3.5f;
[SerializeField] private float predictionSequenceGap = 0.4f;
[SerializeField] private float spreadDistance = 0.35f;
[Header("聚焦表现")]
[SerializeField] private Color focusRingColor = Color.white;
[SerializeField, Range(0.01f, 0.3f)] private float focusRingThickness = 0.08f;
[SerializeField, Range(0.05f, 1f)] private float focusRingAlpha = 0.85f;
[SerializeField] private float statusFadeOutDuration = 0.45f;
[SerializeField] private float shuffleDistance = 0.35f;
[SerializeField] private float shuffleFrequency = 3.5f;
[SerializeField] private float shuffleVerticalScale = 0.6f;
[Header("开场表现")]
[SerializeField] private float entranceDelay = 0.45f;
[SerializeField] private int entranceCoreCount = 8;
[SerializeField] private float entranceCoreRadius = 0.55f;
[SerializeField] private float entranceInitialScale = 0.25f;
[SerializeField] private float entranceCoreBurstDuration = 1f;
[SerializeField] private float entranceCoreStagger = 0.08f;
[SerializeField] private float entranceCascadeDuration = 0.8f;
[SerializeField] private float entranceCascadeStagger = 0.03f;
[SerializeField] private float entranceSearchRadius = 2f;
[Header("渲染层设置")]
[SerializeField] private string renderLayerName = "HuoshanScreen";
private int renderLayer = 0;
[Header("调试/测试")]
[SerializeField] private bool enableDebugShortcuts = true;
[SerializeField] private KeyCode debugCompleteKey = KeyCode.F5;
[SerializeField] private KeyCode debugResetKey = KeyCode.F9;
[SerializeField] private KeyCode debugFullTestKey = KeyCode.F10;
[SerializeField] private bool autoRunDebugTestOnStart = false;
[Header("布局优化")]
[SerializeField] private bool enableSeparation = true; // 是否启用粒子分离力
[SerializeField] private float minParticleSpacing = 0.6f; // 粒子最小间距
[SerializeField] private float separationForce = 2.5f; // 分离力度
[SerializeField] private float lineAlphaMultiplier = 0.65f; // 连接线透明度缩放
// 粒子列表
private List floatingParticles = new List();
private List candidateParticles = new List();
private List targetParticles = new List(); // 目标粒子列表(原 redParticles)
// 效果系统(传播效果已移除,保留空列表供渲染器使用)
private static readonly List s_emptyPropagations = new List();
private Dictionary previousConnections = new Dictionary();
private bool initializationComplete = false;
private int initializationFrames = 0;
// 游戏状态
private bool isCompleted = false;
private CompletionPhase completionPhase = CompletionPhase.None;
private float completionTimer = 0f;
private float fadeOutAlpha = 1f;
private List orderedRedParticles = new List();
private bool allowInput = true;
private float focusTimer = 0f;
private Dictionary focusVisuals = new Dictionary();
private Vector3 focusCentroid;
private List finalTargetCharacters = new List();
private List finalTargetPositions = new List();
private bool interferenceLogicSuspended = false;
private bool statusUIHidden = false;
private float integrationTextBaseAlpha = 1f;
private float interferenceTextBaseAlpha = 1f;
private Coroutine entranceRoutine;
private Coroutine debugTestRoutine;
private readonly Dictionary entranceTargets = new Dictionary();
private class TargetFocusVisual
{
public CandidateParticle particle;
public GameObject ringObject;
public Disc ringDisc;
public TMP_Text probabilityLabel;
public float probability = 90f;
public float targetProbability = 100f;
public float startShakeIntensity;
public string finalChar;
public bool stabilized;
public Vector3 focusPosition;
public Vector3 finalPosition;
public int orderIndex;
}
private bool focusTweensCompleted = false;
// 边界
private Bounds movementBounds;
private Vector3 boundsCenter;
private float boundsRadius; // 圆形时有效
// 连接渲染(使用Shapes)
private ConnectionRenderer connectionRenderer;
// 噪点叠加渲染器(复古显示器效果)
private NoiseOverlayRenderer noiseOverlayRenderer;
[Header("复古显示器效果")]
[SerializeField] private bool enableRetroEffect = true;
[SerializeField, Range(0f, 1f)] private float retroEffectIntensity = 0.5f;
// 是否已经初始化
private bool isInitialized = false;
private void Start()
{
// Start 中不再自动初始化,等待外部调用 InitializeSystem
}
///
/// 初始化系统(可以由外部调用,用于配置游戏参数)
///
/// 焦虑短句列表
/// 目标句子
/// 完成时触发的对话节点
public void InitializeSystem(List phrases, string sentence, string dialogNode = null)
{
// 如果是第一次初始化,先执行基础初始化
if (!isInitialized)
{
InitializeCanvas();
InitializeParticles();
InitializeRenderers();
if (integrationProgressText != null)
{
integrationTextBaseAlpha = integrationProgressText.color.a;
}
if (interferenceCountText != null)
{
interferenceTextBaseAlpha = interferenceCountText.color.a;
}
if (focusSequenceButton != null)
{
focusSequenceButton.interactable = false;
focusSequenceButton.gameObject.SetActive(false);
focusSequenceButton.onClick.RemoveListener(HandleFocusButtonClicked);
focusSequenceButton.onClick.AddListener(HandleFocusButtonClicked);
}
isInitialized = true;
}
// 设置游戏配置
anxietyPhrases = phrases ?? new List();
targetSentence = sentence ?? "这是一个测试示例";
completionDialogNode = dialogNode ?? "";
// 设置全局焦虑短句配置
TextParticle.SetAnxietyPhrases(anxietyPhrases);
// 根据目标句子更新目标粒子数量
redParticleCount = Mathf.Min(sentence.Length, candidateCount);
// 重新初始化游戏状态并播放入场动画
ResetGame(true);
}
///
/// 停止系统(关闭时调用,但保持初始化状态,可以重新开启)
///
public void StopSystem()
{
// 如果未初始化,直接返回
if (!isInitialized)
return;
if (entranceRoutine != null)
{
StopCoroutine(entranceRoutine);
entranceRoutine = null;
}
if (debugTestRoutine != null)
{
StopCoroutine(debugTestRoutine);
debugTestRoutine = null;
}
// 停止所有 DOTween 动画
DOTween.Kill(this);
foreach (var particle in candidateParticles)
{
if (particle != null)
{
particle.transform.DOKill();
}
}
foreach (var particle in floatingParticles)
{
if (particle != null)
{
particle.transform.DOKill();
}
}
// 重置游戏状态(但不重置 isInitialized,这样可以重新开启)
isCompleted = false;
completionPhase = CompletionPhase.None;
allowInput = false;
}
private void InitializeCanvas()
{
// 获取渲染层索引
renderLayer = LayerMask.NameToLayer(renderLayerName);
if (renderLayer == -1)
{
Debug.LogWarning($"Layer '{renderLayerName}' not found, using default layer.");
renderLayer = 0;
}
// 如果没有指定canvas,创建一个
if (worldCanvas == null)
{
GameObject canvasObj = new GameObject("Language Canvas");
canvasObj.transform.SetParent(transform);
canvasObj.transform.localPosition = Vector3.zero;
canvasObj.layer = renderLayer;
worldCanvas = canvasObj.AddComponent