Files
aibis-dream/Assets/Scripts/MiniGame/HuoShan/NewExpress/SentenceBuilder.cs
T
2025-11-03 18:24:38 +08:00

376 lines
13 KiB
C#

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using DG.Tweening;
namespace AibisDream.MiniGame.HuoShan
{
/// <summary>
/// 造句系统 - 处理句子生成流程:选定文字、候选文字、概率变化、连线
/// </summary>
public class SentenceBuilder
{
private BackgroundTextLayer _backgroundLayer;
private CircularViewport _viewport;
private List<TextNode> _sentenceNodes;
private string _targetSentence;
private int _currentIndex = 0;
private bool _isBuilding = false;
private int _stuckIndex = -1; // 卡壳位置,-1表示不卡壳
// 候选文字相关
private List<TextNode> _candidateNodes;
private float _probabilityChangeSpeed = 0.5f;
private Coroutine _buildingCoroutine;
private MonoBehaviour _coroutineRunner;
// 句子连线设置
private float _sentenceLineSpacing = 1.5f;
private Vector2 _sentenceDirection = Vector2.right;
public SentenceBuilder(BackgroundTextLayer backgroundLayer, CircularViewport viewport, MonoBehaviour coroutineRunner)
{
_backgroundLayer = backgroundLayer;
_viewport = viewport;
_coroutineRunner = coroutineRunner;
_sentenceNodes = new List<TextNode>();
_candidateNodes = new List<TextNode>();
}
/// <summary>
/// 开始构建句子
/// </summary>
public void StartBuildingSentence(string sentence, int stuckIndex = -1)
{
if (_isBuilding)
{
StopBuilding();
}
_targetSentence = sentence;
_currentIndex = 0;
_stuckIndex = stuckIndex;
_isBuilding = true;
// 清理之前的句子节点
ClearSentence();
// 开始构建协程
_buildingCoroutine = _coroutineRunner.StartCoroutine(BuildingCoroutine());
}
private IEnumerator BuildingCoroutine()
{
// 等待一帧确保背景层已初始化
yield return null;
while (_currentIndex < _targetSentence.Length && _isBuilding)
{
char targetChar = _targetSentence[_currentIndex];
// 检查是否到达卡壳位置
if (_currentIndex == _stuckIndex)
{
yield return _coroutineRunner.StartCoroutine(HandleStuck());
continue;
}
// 在当前可视范围内找到或创建目标文字
TextNode targetNode = FindOrCreateNodeInViewport(targetChar.ToString());
if (targetNode != null)
{
// 设置为选定状态
SetNodeAsSelected(targetNode, _currentIndex);
_sentenceNodes.Add(targetNode);
// 移动到句子位置
yield return MoveNodeToSentencePosition(targetNode, _currentIndex);
// 如果不是最后一个字,准备下一个字的候选
if (_currentIndex < _targetSentence.Length - 1)
{
yield return _coroutineRunner.StartCoroutine(PrepareNextCandidate());
}
_currentIndex++;
}
yield return new WaitForSeconds(0.5f);
}
_isBuilding = false;
}
private TextNode FindOrCreateNodeInViewport(string text)
{
// 首先尝试在可视范围内找到匹配的文字
var nodesInViewport = _backgroundLayer.GetNodesInRange(_viewport.ViewportCenter, _viewport.ViewportRadius);
var matchingNode = nodesInViewport.FirstOrDefault(n => n.Text == text && n.State == TextNodeState.Random);
if (matchingNode != null)
{
return matchingNode;
}
// 如果找不到,在可视范围内随机选择一个节点并改变其文字
if (nodesInViewport.Count > 0)
{
var randomNode = nodesInViewport[Random.Range(0, nodesInViewport.Count)];
randomNode.Text = text;
randomNode.UpdateVisual();
return randomNode;
}
return null;
}
private void SetNodeAsSelected(TextNode node, int index)
{
node.State = TextNodeState.Selected;
node.SentenceIndex = index;
node.Scale = 1.5f;
node.Brightness = 1f;
node.Color = Color.yellow;
node.UpdateVisual();
// 放大动画
node.GameObject.transform.DOScale(1.5f, 0.3f).SetEase(Ease.OutBack);
}
private IEnumerator MoveNodeToSentencePosition(TextNode node, int index)
{
Vector2 targetPos = GetSentencePosition(index);
// 使用DOTween移动
yield return node.GameObject.transform.DOMove(
new Vector3(targetPos.x, targetPos.y, 0),
0.5f
).SetEase(Ease.OutQuad).WaitForCompletion();
node.Position = targetPos;
}
private Vector2 GetSentencePosition(int index)
{
// 以视口中心为起点,沿方向排列
Vector2 startPos = _viewport.ViewportCenter;
Vector2 offset = _sentenceDirection * index * _sentenceLineSpacing;
return startPos + offset;
}
private IEnumerator PrepareNextCandidate()
{
if (_currentIndex >= _targetSentence.Length - 1) yield break;
char nextChar = _targetSentence[_currentIndex + 1];
// 清除之前的候选
ClearCandidates();
// 在当前选定节点周围找到几个节点作为候选
TextNode selectedNode = _sentenceNodes[_currentIndex];
var nearbyNodes = _backgroundLayer.GetNodesInRange(selectedNode.Position, 3f)
.Where(n => n.State == TextNodeState.Random)
.Take(5)
.ToList();
// 确保目标字符在候选列表中
bool hasTargetChar = false;
foreach (var node in nearbyNodes)
{
if (node.Text == nextChar.ToString())
{
hasTargetChar = true;
break;
}
}
if (!hasTargetChar && nearbyNodes.Count > 0)
{
nearbyNodes[0].Text = nextChar.ToString();
nearbyNodes[0].UpdateVisual();
}
// 设置候选状态
_candidateNodes.Clear();
foreach (var node in nearbyNodes)
{
node.State = TextNodeState.Candidate;
node.Color = Color.cyan;
node.Probability = Random.Range(0f, 0.5f);
node.AddConnection(selectedNode);
selectedNode.AddConnection(node);
node.UpdateVisual();
_candidateNodes.Add(node);
}
// 概率变化动画
yield return _coroutineRunner.StartCoroutine(AnimateCandidateProbabilities(nextChar.ToString()));
}
private IEnumerator AnimateCandidateProbabilities(string targetText)
{
float duration = 2f;
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = elapsed / duration;
foreach (var node in _candidateNodes)
{
if (node.Text == targetText)
{
// 目标字符概率逐渐增加到1
node.Probability = Mathf.Lerp(node.Probability, 1f, t * 0.1f);
}
else
{
// 其他字符概率逐渐降低
node.Probability = Mathf.Lerp(node.Probability, 0f, t * 0.1f);
}
// 根据概率更新亮度
node.Brightness = 0.5f + node.Probability * 0.5f;
node.UpdateVisual();
}
yield return null;
}
// 找到概率最高的节点(应该是目标字符)
var selectedCandidate = _candidateNodes.OrderByDescending(n => n.Probability).FirstOrDefault();
if (selectedCandidate != null && selectedCandidate.Text == targetText)
{
// 清除候选状态,恢复随机
ClearCandidates();
selectedCandidate.State = TextNodeState.Random;
}
}
private IEnumerator HandleStuck()
{
// 生成卡壳的错误文字
string stuckText = "我是个傻逼";
// 在当前位置创建卡壳文字
List<TextNode> stuckNodes = new List<TextNode>();
Vector2 currentPos = GetSentencePosition(_currentIndex);
for (int i = 0; i < stuckText.Length; i++)
{
TextNode stuckNode = FindOrCreateNodeInViewport(stuckText[i].ToString());
if (stuckNode != null)
{
stuckNode.State = TextNodeState.Stuck;
stuckNode.IsStuck = true;
stuckNode.SentenceIndex = _currentIndex + i;
stuckNode.Color = Color.red;
stuckNode.Probability = 1f;
stuckNode.Scale = 1.2f;
stuckNode.Position = currentPos + Vector2.right * i * _sentenceLineSpacing;
stuckNode.UpdateVisual();
stuckNodes.Add(stuckNode);
}
}
// 等待玩家点击移除所有卡壳文字
while (stuckNodes.Count > 0)
{
yield return null;
// 检查点击
if (Input.GetMouseButtonDown(0))
{
Vector2 mousePos = GetMouseWorldPosition();
for (int i = stuckNodes.Count - 1; i >= 0; i--)
{
var node = stuckNodes[i];
float distance = Vector2.Distance(mousePos, node.Position);
if (distance < 1f) // 点击范围
{
// 移除节点
node.State = TextNodeState.Random;
node.IsStuck = false;
node.Color = Color.white;
node.UpdateVisual();
stuckNodes.RemoveAt(i);
// 销毁GameObject
if (node.GameObject != null)
{
Object.Destroy(node.GameObject);
}
}
}
}
}
// 卡壳处理完成,继续构建
}
private Vector2 GetMouseWorldPosition()
{
Camera cam = Camera.main;
if (cam == null) return Vector2.zero;
Vector3 mousePos = Input.mousePosition;
mousePos.z = cam.nearClipPlane + 1f;
Vector3 worldPos = cam.ScreenToWorldPoint(mousePos);
return new Vector2(worldPos.x, worldPos.y);
}
private void ClearCandidates()
{
foreach (var node in _candidateNodes)
{
if (node.State == TextNodeState.Candidate)
{
node.State = TextNodeState.Random;
node.Color = Color.white;
node.Probability = 0f;
node.Brightness = 1f;
node.UpdateVisual();
}
}
_candidateNodes.Clear();
}
private void ClearSentence()
{
foreach (var node in _sentenceNodes)
{
if (node.State == TextNodeState.Selected)
{
node.State = TextNodeState.Random;
node.SentenceIndex = -1;
node.Scale = 1f;
node.Color = Color.white;
node.UpdateVisual();
}
}
_sentenceNodes.Clear();
}
public void StopBuilding()
{
_isBuilding = false;
if (_buildingCoroutine != null)
{
_coroutineRunner.StopCoroutine(_buildingCoroutine);
_buildingCoroutine = null;
}
ClearCandidates();
}
public List<TextNode> GetSentenceNodes()
{
return _sentenceNodes;
}
}
}