Files
aibis-dream/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleManager.cs
T
2025-11-12 19:46:16 +08:00

796 lines
28 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using DG.Tweening;
using Shapes;
using TMPro;
namespace AibisDream.MiniGame.Language
{
/// <summary>
/// 游戏完成阶段
/// </summary>
public enum CompletionPhase
{
None, // 未完成
Arranging, // 排列中
Revealing, // 揭示中
Completed // 已完成
}
/// <summary>
/// 语言粒子系统主管理器
/// </summary>
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<FloatingTextParticle> floatingParticles = new List<FloatingTextParticle>();
private List<CandidateParticle> candidateParticles = new List<CandidateParticle>();
private List<CandidateParticle> redParticles = new List<CandidateParticle>();
// 效果系统
private List<EffectPropagation> effectPropagations = new List<EffectPropagation>();
private Dictionary<string, bool> previousConnections = new Dictionary<string, bool>();
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<CandidateParticle> orderedRedParticles = new List<CandidateParticle>();
private List<Vector3> targetPositions = new List<Vector3>();
// 波形系统
private List<WaveformPoint> waveformPoints = new List<WaveformPoint>();
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<Canvas>();
worldCanvas.renderMode = RenderMode.WorldSpace;
RectTransform rectTransform = canvasObj.GetComponent<RectTransform>();
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<FloatingTextParticle>();
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<CandidateParticle>();
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>();
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>();
connectionRenderer.manager = this;
connectionRenderer.connectionDistance = connectionDistance;
// 波形渲染器
GameObject waveObj = new GameObject("WaveformRenderer");
waveObj.transform.SetParent(transform);
waveObj.transform.localPosition = Vector3.zero;
waveformRenderer = waveObj.AddComponent<WaveformRenderer>();
waveformRenderer.interactionRadius = interactionRadius;
}
private void SelectRedParticles()
{
redParticles.Clear();
List<CandidateParticle> 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<string, bool> currentConnections = new Dictionary<string, bool>();
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<CandidateParticle> visited = new HashSet<CandidateParticle>();
Queue<CandidateParticle> queue = new Queue<CandidateParticle>();
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<CandidateParticle> GetOrderedRedParticles()
{
if (redParticles.Count == 0) return new List<CandidateParticle>();
if (redParticles.Count == 1) return new List<CandidateParticle>(redParticles);
// 从最左边的粒子开始
var ordered = new List<CandidateParticle>();
var startParticle = redParticles.OrderBy(p => p.transform.position.x).First();
HashSet<CandidateParticle> visited = new HashSet<CandidateParticle>();
Stack<CandidateParticle> stack = new Stack<CandidateParticle>();
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();
}
}
}