using UnityEngine; using System.Collections.Generic; using System.Linq; using DG.Tweening; using Shapes; using TMPro; namespace AibisDream.MiniGame.Language { /// /// 游戏完成阶段 /// public enum CompletionPhase { None, // 未完成 Arranging, // 排列中 Revealing, // 揭示中 Completed // 已完成 } /// /// 语言粒子系统主管理器 /// public class LanguageParticleManager : MonoBehaviour { [Header("Canvas设置")] [SerializeField] private Canvas worldCanvas; [SerializeField] private float canvasSize = 10f; [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 string targetSentence = "别过来我感觉害怕"; [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; // 粒子列表 private List floatingParticles = new List(); private List candidateParticles = new List(); private List redParticles = new List(); // 效果系统 private List effectPropagations = 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 List targetPositions = new List(); // 波形系统 private List waveformPoints = new List(); private float calmProgress = 0f; private Vector2 lastMousePos; private float mouseDragSpeed; // 边界 private Bounds movementBounds; // 连接渲染(使用Shapes) private ConnectionRenderer connectionRenderer; private WaveformRenderer waveformRenderer; private void Start() { InitializeCanvas(); InitializeParticles(); InitializeRenderers(); } private void InitializeCanvas() { // 如果没有指定canvas,创建一个 if (worldCanvas == null) { GameObject canvasObj = new GameObject("Language Canvas"); canvasObj.transform.SetParent(transform); canvasObj.transform.localPosition = Vector3.zero; worldCanvas = canvasObj.AddComponent(); worldCanvas.renderMode = RenderMode.WorldSpace; RectTransform rectTransform = canvasObj.GetComponent(); rectTransform.sizeDelta = new Vector2(canvasSize, canvasSize); } // 确保 Canvas 配置正确 if (worldCanvas != null) { // 设置 WorldSpace Canvas 的相机 if (worldCanvas.worldCamera == null) { worldCanvas.worldCamera = Camera.main; } } // 设置运动边界 float halfSize = canvasSize / 2f - textMargin; movementBounds = new Bounds( worldCanvas.transform.position, new Vector3(halfSize * 2f, halfSize * 2f, 0f) ); } private void InitializeParticles() { // 创建背景浮动文字 for (int i = 0; i < floatingCount; i++) { Vector3 pos = GetRandomPositionInBounds(); GameObject obj = CreateParticleObject(pos, $"FloatingParticle_{i}"); FloatingTextParticle particle = obj.AddComponent(); particle.SetFont(chineseFontAsset); particle.SetMovementBounds(movementBounds); floatingParticles.Add(particle); } // 创建候选粒子 for (int i = 0; i < candidateCount; i++) { Vector3 pos = GetRandomPositionInBounds(); GameObject obj = CreateParticleObject(pos, $"CandidateParticle_{i}"); CandidateParticle particle = obj.AddComponent(); particle.SetFont(chineseFontAsset); particle.SetMovementBounds(movementBounds); candidateParticles.Add(particle); } // 选择红色粒子 SelectRedParticles(); } private GameObject CreateParticleObject(Vector3 position, string name) { GameObject obj = new GameObject(name); // 添加 RectTransform(在 Canvas 下必需) RectTransform rectTransform = obj.AddComponent(); rectTransform.SetParent(worldCanvas.transform, false); rectTransform.position = position; rectTransform.localScale = Vector3.one; rectTransform.sizeDelta = Vector2.zero; // 父对象不需要大小 return obj; } private Vector3 GetRandomPositionInBounds() { float x = Random.Range(movementBounds.min.x, movementBounds.max.x); float y = Random.Range(movementBounds.min.y, movementBounds.max.y); return new Vector3(x, y, 0f); } private void InitializeRenderers() { // 连接线渲染器 GameObject connObj = new GameObject("ConnectionRenderer"); connObj.transform.SetParent(transform); connObj.transform.localPosition = Vector3.zero; connectionRenderer = connObj.AddComponent(); connectionRenderer.manager = this; connectionRenderer.connectionDistance = connectionDistance; // 波形渲染器 GameObject waveObj = new GameObject("WaveformRenderer"); waveObj.transform.SetParent(transform); waveObj.transform.localPosition = Vector3.zero; waveformRenderer = waveObj.AddComponent(); waveformRenderer.interactionRadius = interactionRadius; } private void SelectRedParticles() { redParticles.Clear(); List shuffled = candidateParticles.OrderBy(x => Random.value).ToList(); float minDistance = Mathf.Min(movementBounds.size.x, movementBounds.size.y) / (redParticleCount * 0.8f); foreach (var particle in shuffled) { if (redParticles.Count >= redParticleCount) break; bool tooClose = false; foreach (var selected in redParticles) { if (Vector3.Distance(particle.transform.position, selected.transform.position) < minDistance) { tooClose = true; break; } } if (!tooClose || redParticles.Count == 0) { particle.isRed = true; particle.originalIsRed = true; redParticles.Add(particle); } } // 如果不够,强制添加 int remaining = redParticleCount - redParticles.Count; for (int i = 0; i < remaining && i < shuffled.Count; i++) { if (!shuffled[i].isRed) { shuffled[i].isRed = true; shuffled[i].originalIsRed = true; redParticles.Add(shuffled[i]); } } } private void Update() { // 初始化检查 if (!initializationComplete) { initializationFrames++; if (initializationFrames >= 30) { initializationComplete = true; } else if (initializationFrames >= 20) { // 建立初始连接状态 BuildConnectionMap(); } } // 更新连接 UpdateConnections(); // 检测连接变化(触发效果传播) if (initializationComplete && completionPhase == CompletionPhase.None) { DetectConnectionChanges(); } // 更新效果传播 UpdateEffectPropagations(); // 检测完成状态 if (!isCompleted && completionPhase == CompletionPhase.None) { if (CheckRedParticlesConnected()) { isCompleted = true; StartCompletionSequence(); } } // 更新完成动画 if (isCompleted) { UpdateCompletionAnimation(); } // 更新波形 if (completionPhase == CompletionPhase.Revealing || completionPhase == CompletionPhase.Completed) { UpdateWaveform(); } // 更新输入 UpdateInput(); // 更新渲染器数据 UpdateRenderers(); } private void UpdateRenderers() { // 更新连接线渲染器 if (connectionRenderer != null) { connectionRenderer.SetData( candidateParticles, effectPropagations, completionPhase, fadeOutAlpha ); } // 更新波形渲染器 if (waveformRenderer != null) { Vector2 mousePos = GetMouseWorldPosition(); bool mouseDown = Input.GetMouseButton(0); waveformRenderer.SetData( waveformPoints, completionPhase, calmProgress, mousePos, mouseDown, mouseDragSpeed ); } } private void UpdateConnections() { // 重置所有连接 foreach (var particle in candidateParticles) { particle.connections.Clear(); } // 检测连接 for (int i = 0; i < candidateParticles.Count; i++) { for (int j = i + 1; j < candidateParticles.Count; j++) { float distance = Vector3.Distance( candidateParticles[i].transform.position, candidateParticles[j].transform.position ); if (distance < connectionDistance) { candidateParticles[i].connections.Add(candidateParticles[j]); candidateParticles[j].connections.Add(candidateParticles[i]); } } } } private void BuildConnectionMap() { previousConnections.Clear(); for (int i = 0; i < candidateParticles.Count; i++) { for (int j = i + 1; j < candidateParticles.Count; j++) { float distance = Vector3.Distance( candidateParticles[i].transform.position, candidateParticles[j].transform.position ); if (distance < connectionDistance) { string key = $"{Mathf.Min(i, j)}-{Mathf.Max(i, j)}"; previousConnections[key] = true; } } } } private void DetectConnectionChanges() { Dictionary currentConnections = new Dictionary(); for (int i = 0; i < candidateParticles.Count; i++) { for (int j = i + 1; j < candidateParticles.Count; j++) { float distance = Vector3.Distance( candidateParticles[i].transform.position, candidateParticles[j].transform.position ); if (distance < connectionDistance) { string key = $"{Mathf.Min(i, j)}-{Mathf.Max(i, j)}"; currentConnections[key] = true; // 检测新连接 if (!previousConnections.ContainsKey(key)) { var p1 = candidateParticles[i]; var p2 = candidateParticles[j]; // 检查是否是红色粒子与蓝色粒子的新连接 bool isRedToBlue = (p1.isRed && !p2.isRed && !p2.isOrange && p1.originalIsRed) || (!p1.isRed && !p1.isOrange && p2.isRed && p2.originalIsRed); if (isRedToBlue) { var redParticle = p1.isRed ? p1 : p2; var blueParticle = p1.isRed ? p2 : p1; if (!blueParticle.isStatic && !blueParticle.hasEffect) { TriggerEffectPropagation(redParticle, blueParticle); } } } } } } previousConnections = currentConnections; } private void TriggerEffectPropagation(CandidateParticle redParticle, CandidateParticle blueParticle) { effectPropagations.Add(new EffectPropagation(redParticle, 1f)); } private void UpdateEffectPropagations() { for (int i = effectPropagations.Count - 1; i >= 0; i--) { effectPropagations[i].Update(Time.deltaTime); if (!effectPropagations[i].IsActive) { effectPropagations[i].Cleanup(); effectPropagations.RemoveAt(i); } } } private bool CheckRedParticlesConnected() { if (redParticles.Count == 0) return false; if (redParticles.Count == 1) return true; // 检查所有红色粒子是否连通 HashSet visited = new HashSet(); Queue queue = new Queue(); queue.Enqueue(redParticles[0]); visited.Add(redParticles[0]); while (queue.Count > 0) { var current = queue.Dequeue(); foreach (var connected in current.connections) { if (connected.isRed && !visited.Contains(connected)) { visited.Add(connected); queue.Enqueue(connected); } } } if (visited.Count != redParticles.Count) return false; // 检查红色粒子是否与蓝色粒子分离 foreach (var red in redParticles) { foreach (var other in candidateParticles) { if (other.isRed) continue; float dist = Vector3.Distance(red.transform.position, other.transform.position); if (dist < connectionDistance) return false; } } return true; } private void StartCompletionSequence() { completionPhase = CompletionPhase.Arranging; completionTimer = 0f; fadeOutAlpha = 1f; // 重置波形 calmProgress = 0f; waveformPoints.Clear(); // 获取排序的红色粒子 orderedRedParticles = GetOrderedRedParticles(); int displayLength = Mathf.Min(orderedRedParticles.Count, targetSentence.Length); // 计算目标位置 float spacing = 0.8f; float totalWidth = displayLength * spacing; float startX = -totalWidth / 2f + spacing / 2f; float centerY = 0f; targetPositions.Clear(); for (int i = 0; i < displayLength; i++) { targetPositions.Add(new Vector3(startX + i * spacing, centerY, 0f)); } if (orderedRedParticles.Count > displayLength) { orderedRedParticles = orderedRedParticles.Take(displayLength).ToList(); } // 清理效果 effectPropagations.Clear(); // 停止非红色粒子 foreach (var p in candidateParticles) { if (!p.isRed) { p.velocity = Vector2.zero; if (p.isOrange) { p.RemoveEffect(); } } } } private List GetOrderedRedParticles() { if (redParticles.Count == 0) return new List(); if (redParticles.Count == 1) return new List(redParticles); // 从最左边的粒子开始 var ordered = new List(); var startParticle = redParticles.OrderBy(p => p.transform.position.x).First(); HashSet visited = new HashSet(); Stack stack = new Stack(); stack.Push(startParticle); visited.Add(startParticle); while (stack.Count > 0) { var current = stack.Pop(); ordered.Add(current); foreach (var connected in current.connections) { if (connected.isRed && !visited.Contains(connected)) { visited.Add(connected); stack.Push(connected); } } } return ordered; } private void UpdateCompletionAnimation() { completionTimer += Time.deltaTime; if (completionPhase == CompletionPhase.Arranging) { bool allArrived = true; for (int i = 0; i < orderedRedParticles.Count; i++) { var particle = orderedRedParticles[i]; var target = targetPositions[i]; particle.transform.position = Vector3.Lerp( particle.transform.position, target, Time.deltaTime * 5f ); if (Vector3.Distance(particle.transform.position, target) > 0.01f) { allArrived = false; } } fadeOutAlpha = Mathf.Max(0f, fadeOutAlpha - Time.deltaTime * 2f); if (allArrived && completionTimer > 0.5f) { completionPhase = CompletionPhase.Revealing; completionTimer = 0f; InitializeWaveform(); // 初始化抖动效果 foreach (var p in orderedRedParticles) { p.InitializeAnxietyEffect(); } } } else if (completionPhase == CompletionPhase.Revealing) { float revealDelay = 0.25f; int currentIndex = Mathf.FloorToInt(completionTimer / revealDelay); for (int i = 0; i < orderedRedParticles.Count; i++) { var p = orderedRedParticles[i]; if (i < currentIndex) { // 已显示 if (i < targetSentence.Length) { p.currentChar = targetSentence[i].ToString(); p.isStatic = false; } } else if (i == currentIndex) { // 正在显示(闪烁) p.changeSpeedMultiplier = 20f; if (completionTimer % revealDelay >= revealDelay - 0.1f) { if (i < targetSentence.Length) { p.currentChar = targetSentence[i].ToString(); } } } } if (currentIndex >= orderedRedParticles.Count) { completionPhase = CompletionPhase.Completed; for (int i = 0; i < orderedRedParticles.Count && i < targetSentence.Length; i++) { orderedRedParticles[i].currentChar = targetSentence[i].ToString(); orderedRedParticles[i].isStatic = false; orderedRedParticles[i].changeSpeedMultiplier = 1f; } } } else if (completionPhase == CompletionPhase.Completed) { // 检查是否所有文字都已消散 bool allDissolved = orderedRedParticles.All(p => p.dissolveProgress >= 1f); if (allDissolved && completionTimer > 1f) { ResetGame(); } } } private void InitializeWaveform() { waveformPoints.Clear(); if (orderedRedParticles.Count == 0) return; float baseY = 0f; for (int i = 0; i < orderedRedParticles.Count; i++) { var particle = orderedRedParticles[i]; var point = new WaveformPoint( particle.transform.position.x, baseY, waveformAmplitude, i, particle ); waveformPoints.Add(point); particle.waveformPoint = point; } lastMousePos = GetMouseWorldPosition(); } private void UpdateWaveform() { if (waveformPoints.Count == 0) { InitializeWaveform(); return; } Vector2 mousePos = GetMouseWorldPosition(); mouseDragSpeed = Vector2.Distance(mousePos, lastMousePos) / Time.deltaTime; lastMousePos = mousePos; bool mousePressed = Input.GetMouseButton(0); foreach (var point in waveformPoints) { point.Update(0f, waveformAmplitude, mousePos, mousePressed, mouseDragSpeed, interactionRadius); } // 更新整体平静进度 if (waveformPoints.Count > 0) { calmProgress = waveformPoints.Average(p => p.calmAmount); } } private void UpdateInput() { // 只在第一阶段(未完成)时处理鼠标输入 if (completionPhase != CompletionPhase.None) return; Vector2 mouseWorldPos = GetMouseWorldPosition(); // 左键点击:排斥附近粒子 if (Input.GetMouseButton(0)) { ApplyForceToNearbyParticles(mouseWorldPos, mouseInteractionRadius, repulsionForce); } // 右键点击:吸引附近粒子 if (Input.GetMouseButton(1)) { ApplyForceToNearbyParticles(mouseWorldPos, mouseInteractionRadius, -attractionForce); } } private void ApplyForceToNearbyParticles(Vector2 center, float repulsionRadius, float repulsionForce) { foreach (var particle in candidateParticles) { Vector2 particlePos = particle.transform.position; float distance = Vector2.Distance(center, particlePos); if (distance < repulsionRadius && distance > 0.01f) { // 计算方向和力度 Vector2 direction = (particlePos - center).normalized; float forceMagnitude = repulsionForce * (1f - distance / repulsionRadius); // 应用力 particle.ApplyForce(direction * forceMagnitude); } } } private Vector2 GetMouseWorldPosition() { Vector3 mousePos = Input.mousePosition; Camera cam = Camera.main; if (cam != null) { // 对于正交相机,需要使用正确的Z距离 float zDistance = cam.transform.position.z - worldCanvas.transform.position.z; Vector3 worldPos = cam.ScreenToWorldPoint(new Vector3(mousePos.x, mousePos.y, Mathf.Abs(zDistance))); return new Vector2(worldPos.x, worldPos.y); } return Vector2.zero; } private void ResetGame() { isCompleted = false; completionPhase = CompletionPhase.None; completionTimer = 0f; fadeOutAlpha = 1f; orderedRedParticles.Clear(); targetPositions.Clear(); effectPropagations.Clear(); previousConnections.Clear(); initializationComplete = false; initializationFrames = 0; calmProgress = 0f; waveformPoints.Clear(); // 重置所有粒子 foreach (var p in candidateParticles) { p.isRed = false; p.isOrange = false; p.hasEffect = false; p.isStatic = false; p.originalIsRed = false; p.changeSpeedMultiplier = 1f; p.vibrationOffset = Vector2.zero; p.velocity = new Vector2(Random.Range(-0.5f, 0.5f), Random.Range(-0.5f, 0.5f)); p.transform.position = GetRandomPositionInBounds(); p.calmProgress = 0f; p.dissolveProgress = 0f; p.isCalmed = false; p.waveformPoint = null; } SelectRedParticles(); } } }