fix(huoshan): 修复多轮表达重叠并支持非目标粒子数配置
Made-with: Cursor
This commit is contained in:
@@ -100,7 +100,8 @@ namespace AibisDream
|
||||
/// <param name="anxietyPhrases">干扰短句列表(笑话等,非目标粒子从中随机取字符)</param>
|
||||
/// <param name="targetSentence">目标句子</param>
|
||||
/// <param name="completionNodeName">完成时触发的对话节点名称(可选)</param>
|
||||
public void StartSystem(List<string> anxietyPhrases, string targetSentence, string completionNodeName = null)
|
||||
/// <param name="nonTargetParticleTotal">非目标候选粒子总数;<0 时使用 Inspector 默认候选池大小</param>
|
||||
public void StartSystem(List<string> anxietyPhrases, string targetSentence, string completionNodeName = null, int nonTargetParticleTotal = -1)
|
||||
{
|
||||
if (particleManager != null)
|
||||
{
|
||||
@@ -108,17 +109,17 @@ namespace AibisDream
|
||||
var phrases = anxietyPhrases ?? defaultAnxietyPhrases;
|
||||
var sentence = targetSentence ?? defaultTargetSentence;
|
||||
|
||||
particleManager.InitializeSystem(phrases, sentence, completionNodeName);
|
||||
particleManager.InitializeSystem(phrases, sentence, completionNodeName, nonTargetParticleTotal);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动粒子系统(字符串数组版本,方便 Yarn 调用)
|
||||
/// </summary>
|
||||
public void StartSystem(string[] anxietyPhrases, string targetSentence, string completionNodeName = null)
|
||||
public void StartSystem(string[] anxietyPhrases, string targetSentence, string completionNodeName = null, int nonTargetParticleTotal = -1)
|
||||
{
|
||||
List<string> phrasesList = anxietyPhrases != null ? new List<string>(anxietyPhrases) : null;
|
||||
StartSystem(phrasesList, targetSentence, completionNodeName);
|
||||
StartSystem(phrasesList, targetSentence, completionNodeName, nonTargetParticleTotal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -131,6 +132,16 @@ namespace AibisDream
|
||||
yield return particleManager.FadeInReleaseLogIfNeeded(duration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 再次 <c>start_expression</c> 前淡出上一轮粒子与连线(若尚未初始化则立即结束)。
|
||||
/// duration≤0 时使用 LanguageParticleManager 上的默认时长。
|
||||
/// </summary>
|
||||
public IEnumerator FadeOutParticlesBeforeNewRound(float duration = 0f)
|
||||
{
|
||||
if (particleManager == null) yield break;
|
||||
yield return StartCoroutine(particleManager.FadeOutBeforeNewExpressionRound(duration));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 等待表达流程就绪(开场表现 + 火山表达panel 播完)
|
||||
/// </summary>
|
||||
|
||||
@@ -113,6 +113,16 @@ namespace AibisDream.MiniGame.Language
|
||||
[SerializeField] private TMP_Text integrationProgressText;
|
||||
[SerializeField] private TMP_Text interferenceCountText;
|
||||
|
||||
[Header("完成/确认阶段 — 屏幕内显示范围")]
|
||||
[Tooltip("为真时,完成连线后(含确认按钮、聚焦、稳定化)目标字符位置会被限制在裁剪区域内,避免超出电视机屏幕可视区")]
|
||||
[SerializeField] private bool clampCompletionPhasePositions = true;
|
||||
[Tooltip("可选:场景中一块与屏幕内框对齐的 RectTransform(World Space Canvas 子物体即可)。不指定则用粒子用的 movementBounds(canvasSize - textMargin)")]
|
||||
[SerializeField] private RectTransform completionScreenClipRect;
|
||||
[Tooltip("在裁剪区基础上再向内缩进的世界单位距离,避免放大后的字半个挂在边外")]
|
||||
[SerializeField] private float completionClipEdgePadding = 0.2f;
|
||||
[Tooltip("将目标粒子拉回屏幕裁剪区时的平滑时间(越大越柔和,略增加拖尾感)")]
|
||||
[SerializeField, Range(0.02f, 0.55f)] private float completionPositionSmoothTime = 0.2f;
|
||||
|
||||
[Header("完成阶段 UI")]
|
||||
[SerializeField] private GameObject languageDeepPanel1;
|
||||
[SerializeField] private GameObject languageDeepPanel2;
|
||||
@@ -126,6 +136,8 @@ namespace AibisDream.MiniGame.Language
|
||||
[SerializeField] private float focusZoomScale = 1.6f;
|
||||
[SerializeField] private float focusMoveDuration = 1.2f;
|
||||
[SerializeField] private float focusFadeDuration = 1f;
|
||||
[Tooltip("再次 start_expression 时,若上一轮粒子/UI 仍在,先淡出再重开;协程 duration≤0 时用此值(秒)")]
|
||||
[SerializeField] private float restartExpressionFadeDuration = 0.55f;
|
||||
[SerializeField] private float probabilityDuration = 3.5f;
|
||||
[SerializeField] private float predictionSequenceGap = 0.4f;
|
||||
[SerializeField] private float spreadDistance = 0.35f;
|
||||
@@ -187,6 +199,8 @@ namespace AibisDream.MiniGame.Language
|
||||
private bool allowInput = true;
|
||||
private float focusTimer = 0f;
|
||||
private Dictionary<CandidateParticle, TargetFocusVisual> focusVisuals = new Dictionary<CandidateParticle, TargetFocusVisual>();
|
||||
/// <summary>完成阶段屏幕钳制用 SmoothDamp 速度缓存(按粒子)</summary>
|
||||
private readonly Dictionary<CandidateParticle, Vector3> completionClampSmoothVelocity = new Dictionary<CandidateParticle, Vector3>();
|
||||
private Vector3 focusCentroid;
|
||||
private List<string> finalTargetCharacters = new List<string>();
|
||||
private List<Vector3> finalTargetPositions = new List<Vector3>();
|
||||
@@ -236,6 +250,17 @@ namespace AibisDream.MiniGame.Language
|
||||
// 是否已经初始化
|
||||
private bool isInitialized = false;
|
||||
|
||||
/// <summary>Inspector 中的候选粒子总数,用于未在 Yarn 中指定非目标数量时恢复默认</summary>
|
||||
private int defaultCandidateCountFromInspector;
|
||||
private bool hasCapturedDefaultCandidateCount;
|
||||
|
||||
private void CaptureDefaultCandidateCountFromInspector()
|
||||
{
|
||||
if (hasCapturedDefaultCandidateCount) return;
|
||||
defaultCandidateCountFromInspector = candidateCount;
|
||||
hasCapturedDefaultCandidateCount = true;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// Start 中不再自动初始化,等待外部调用 InitializeSystem
|
||||
@@ -247,8 +272,24 @@ namespace AibisDream.MiniGame.Language
|
||||
/// <param name="phrases">焦虑短句列表</param>
|
||||
/// <param name="sentence">目标句子</param>
|
||||
/// <param name="dialogNode">完成时触发的对话节点</param>
|
||||
public void InitializeSystem(List<string> phrases, string sentence, string dialogNode = null)
|
||||
/// <param name="nonTargetParticleTotal">
|
||||
/// 非目标候选粒子总数(蓝/干扰侧可交互粒子数量)。≥0 时候选池大小 = 目标句字数 + 该值;<0 时使用 Inspector 的 Candidate Count。
|
||||
/// </param>
|
||||
public void InitializeSystem(List<string> phrases, string sentence, string dialogNode = null, int nonTargetParticleTotal = -1)
|
||||
{
|
||||
CaptureDefaultCandidateCountFromInspector();
|
||||
|
||||
string useSentence = string.IsNullOrEmpty(sentence) ? "这是一个测试示例" : sentence;
|
||||
|
||||
if (nonTargetParticleTotal >= 0)
|
||||
{
|
||||
candidateCount = Mathf.Max(useSentence.Length + nonTargetParticleTotal, useSentence.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
candidateCount = defaultCandidateCountFromInspector;
|
||||
}
|
||||
|
||||
// 如果是第一次初始化,先执行基础初始化
|
||||
if (!isInitialized)
|
||||
{
|
||||
@@ -280,22 +321,120 @@ namespace AibisDream.MiniGame.Language
|
||||
|
||||
isInitialized = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
EnsureCandidatePoolSize(candidateCount);
|
||||
}
|
||||
|
||||
// 设置游戏配置
|
||||
anxietyPhrases = phrases ?? new List<string>();
|
||||
targetSentence = sentence ?? "这是一个测试示例";
|
||||
targetSentence = useSentence;
|
||||
completionDialogNode = dialogNode ?? "";
|
||||
|
||||
// 设置全局焦虑短句配置
|
||||
TextParticle.SetAnxietyPhrases(anxietyPhrases);
|
||||
|
||||
// 根据目标句子更新目标粒子数量
|
||||
redParticleCount = Mathf.Min(sentence.Length, candidateCount);
|
||||
redParticleCount = Mathf.Min(targetSentence.Length, candidateCount);
|
||||
|
||||
// 重新初始化游戏状态并播放入场动画
|
||||
ResetGame(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在再次 <c>start_expression</c> 前调用:若系统已初始化过,则淡出当前粒子、连线与相关 UI,避免与新一轮重叠。
|
||||
/// duration≤0 时使用 <see cref="restartExpressionFadeDuration"/>。
|
||||
/// </summary>
|
||||
public IEnumerator FadeOutBeforeNewExpressionRound(float duration)
|
||||
{
|
||||
if (!isInitialized)
|
||||
yield break;
|
||||
|
||||
float d = duration > 0f ? duration : restartExpressionFadeDuration;
|
||||
|
||||
// 火山释放 log 与文字同时叠在屏幕上时,先把 log 藏起来,等文字淡出结束后再由 FadeInReleaseLogIfNeeded 出现
|
||||
HideReleaseLogImmediate();
|
||||
|
||||
if (entranceRoutine != null)
|
||||
{
|
||||
StopCoroutine(entranceRoutine);
|
||||
entranceRoutine = null;
|
||||
}
|
||||
if (expressionPanelRoutine != null)
|
||||
{
|
||||
StopCoroutine(expressionPanelRoutine);
|
||||
expressionPanelRoutine = null;
|
||||
}
|
||||
if (confirmTimelineRoutine != null)
|
||||
{
|
||||
StopCoroutine(confirmTimelineRoutine);
|
||||
confirmTimelineRoutine = null;
|
||||
}
|
||||
|
||||
DOTween.Kill(this);
|
||||
foreach (var p in candidateParticles)
|
||||
{
|
||||
if (p != null)
|
||||
{
|
||||
DOTween.Kill(p);
|
||||
p.transform.DOKill();
|
||||
p.velocity = Vector2.zero;
|
||||
p.isStatic = true;
|
||||
}
|
||||
}
|
||||
foreach (var f in floatingParticles)
|
||||
{
|
||||
if (f != null)
|
||||
{
|
||||
DOTween.Kill(f);
|
||||
f.transform.DOKill();
|
||||
f.velocity = Vector2.zero;
|
||||
f.isCalmed = true;
|
||||
}
|
||||
}
|
||||
|
||||
interferenceLogicSuspended = true;
|
||||
allowInput = false;
|
||||
isCompleted = false;
|
||||
completionPhase = CompletionPhase.None;
|
||||
|
||||
DestroyFocusVisualDecorationsAndClear();
|
||||
|
||||
DOTween.To(() => fadeOutAlpha, v => fadeOutAlpha = v, 0f, d)
|
||||
.SetEase(Ease.OutQuad)
|
||||
.SetTarget(this);
|
||||
|
||||
if (connectionRenderer != null)
|
||||
{
|
||||
DOTween.To(() => connectionRenderer.NonTargetAlphaScale, v => connectionRenderer.NonTargetAlphaScale = v, 0f, d)
|
||||
.SetEase(Ease.OutQuad)
|
||||
.SetTarget(this);
|
||||
DOTween.To(() => connectionRenderer.TargetAlphaScale, v => connectionRenderer.TargetAlphaScale = v, 0f, d)
|
||||
.SetEase(Ease.OutQuad)
|
||||
.SetTarget(this);
|
||||
}
|
||||
|
||||
foreach (var p in candidateParticles)
|
||||
{
|
||||
if (p != null)
|
||||
FadeOutTextParticle(p, d);
|
||||
}
|
||||
foreach (var f in floatingParticles)
|
||||
{
|
||||
if (f != null)
|
||||
FadeOutTextParticle(f, d);
|
||||
}
|
||||
|
||||
SetStatusUIVisibility(false, d);
|
||||
if (focusSequenceButton != null && focusSequenceButton.gameObject.activeSelf)
|
||||
FadeOutUIElement(focusSequenceButton.gameObject, d);
|
||||
FadeOutUIElement(languageDeepPanel1, d);
|
||||
FadeOutUIElement(languageDeepPanel2, d);
|
||||
|
||||
yield return new WaitForSeconds(d);
|
||||
fadeOutAlpha = 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在打开视图前设置所有初始状态(panels、button 等不激活)
|
||||
/// </summary>
|
||||
@@ -378,6 +517,7 @@ namespace AibisDream.MiniGame.Language
|
||||
isCompleted = false;
|
||||
completionPhase = CompletionPhase.None;
|
||||
allowInput = false;
|
||||
ClearCompletionClampSmoothVelocity();
|
||||
}
|
||||
|
||||
private void InitializeCanvas()
|
||||
@@ -463,23 +603,7 @@ namespace AibisDream.MiniGame.Language
|
||||
// 创建候选粒子
|
||||
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.SetColors(targetParticleColor, nonTargetParticleColor, nonTargetParticleColor, targetCalmColor);
|
||||
particle.SetFontStyles(targetParticleFontSize, nonTargetParticleFontSize, targetParticleBold, nonTargetParticleBold);
|
||||
particle.SetInterferenceParams(nonTargetShakeIntensity, nonTargetShakeSpeed, nonTargetChangeInterval, nonTargetFloatAmplitude);
|
||||
SetParticleBounds(particle);
|
||||
|
||||
// 设置发光效果(目标粒子和非目标粒子使用不同强度)
|
||||
if (enableBloomGlow)
|
||||
{
|
||||
particle.SetGlowSettings(true, textGlowIntensity, nonTargetParticleColor);
|
||||
particle.SetUnderlaySettings(glowDilate, glowSoftness);
|
||||
}
|
||||
|
||||
candidateParticles.Add(particle);
|
||||
AddOneCandidateParticle(i);
|
||||
}
|
||||
|
||||
// 选择红色粒子
|
||||
@@ -501,6 +625,52 @@ namespace AibisDream.MiniGame.Language
|
||||
return obj;
|
||||
}
|
||||
|
||||
private void AddOneCandidateParticle(int index)
|
||||
{
|
||||
Vector3 pos = GetRandomPositionInBounds();
|
||||
GameObject obj = CreateParticleObject(pos, $"CandidateParticle_{index}");
|
||||
CandidateParticle particle = obj.AddComponent<CandidateParticle>();
|
||||
particle.SetFont(chineseFontAsset);
|
||||
particle.SetColors(targetParticleColor, nonTargetParticleColor, nonTargetParticleColor, targetCalmColor);
|
||||
particle.SetFontStyles(targetParticleFontSize, nonTargetParticleFontSize, targetParticleBold, nonTargetParticleBold);
|
||||
particle.SetInterferenceParams(nonTargetShakeIntensity, nonTargetShakeSpeed, nonTargetChangeInterval, nonTargetFloatAmplitude);
|
||||
SetParticleBounds(particle);
|
||||
|
||||
if (enableBloomGlow)
|
||||
{
|
||||
particle.SetGlowSettings(true, textGlowIntensity, nonTargetParticleColor);
|
||||
particle.SetUnderlaySettings(glowDilate, glowSoftness);
|
||||
}
|
||||
|
||||
candidateParticles.Add(particle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增删候选粒子以匹配目标数量(用于 Yarn 动态指定非目标粒子总数后复用同一视图)
|
||||
/// </summary>
|
||||
private void EnsureCandidatePoolSize(int targetSize)
|
||||
{
|
||||
if (targetSize < 0) targetSize = 0;
|
||||
candidateCount = targetSize;
|
||||
|
||||
while (candidateParticles.Count < targetSize)
|
||||
{
|
||||
AddOneCandidateParticle(candidateParticles.Count);
|
||||
}
|
||||
|
||||
while (candidateParticles.Count > targetSize)
|
||||
{
|
||||
int last = candidateParticles.Count - 1;
|
||||
CandidateParticle p = candidateParticles[last];
|
||||
candidateParticles.RemoveAt(last);
|
||||
if (p != null)
|
||||
{
|
||||
p.transform.DOKill();
|
||||
Destroy(p.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Vector3 GetRandomPositionInBounds()
|
||||
{
|
||||
if (layoutShape == LayoutShape.Circle)
|
||||
@@ -686,6 +856,96 @@ namespace AibisDream.MiniGame.Language
|
||||
UpdateRenderers();
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (!isInitialized || !clampCompletionPhasePositions || !isCompleted)
|
||||
return;
|
||||
if (completionPhase == CompletionPhase.None)
|
||||
return;
|
||||
// 连线刚完成、等待确认时保持粒子当前世界坐标,避免 GetCompletionClipBounds(含 padding/屏幕 Rect)
|
||||
// 比游玩用 movementBounds 更紧时,在 Completed 首帧 LateUpdate 产生生硬瞬移。
|
||||
if (completionPhase == CompletionPhase.Completed)
|
||||
return;
|
||||
|
||||
Bounds clip = GetCompletionClipBounds();
|
||||
if (clip.size.x < 0.01f || clip.size.y < 0.01f)
|
||||
return;
|
||||
|
||||
float dt = Time.deltaTime;
|
||||
if (dt < 1e-6f)
|
||||
dt = 1e-6f;
|
||||
float smooth = Mathf.Max(0.0001f, completionPositionSmoothTime);
|
||||
|
||||
for (int i = 0; i < targetParticles.Count; i++)
|
||||
{
|
||||
var p = targetParticles[i];
|
||||
if (p == null) continue;
|
||||
Vector3 cur = p.transform.position;
|
||||
Vector3 desired = ClampPositionToBounds(cur, clip);
|
||||
if (!completionClampSmoothVelocity.TryGetValue(p, out Vector3 vel))
|
||||
vel = Vector3.zero;
|
||||
Vector3 next = Vector3.SmoothDamp(cur, desired, ref vel, smooth, Mathf.Infinity, dt);
|
||||
completionClampSmoothVelocity[p] = vel;
|
||||
p.transform.position = next;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearCompletionClampSmoothVelocity()
|
||||
{
|
||||
completionClampSmoothVelocity.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 完成阶段使用的裁剪范围:优先用 Inspector 指定的屏幕 Rect,否则与粒子运动边界一致。
|
||||
/// </summary>
|
||||
private Bounds GetCompletionClipBounds()
|
||||
{
|
||||
Bounds b;
|
||||
if (completionScreenClipRect != null)
|
||||
{
|
||||
var corners = new Vector3[4];
|
||||
completionScreenClipRect.GetWorldCorners(corners);
|
||||
float minX = float.MaxValue, maxX = float.MinValue;
|
||||
float minY = float.MaxValue, maxY = float.MinValue;
|
||||
for (int c = 0; c < 4; c++)
|
||||
{
|
||||
minX = Mathf.Min(minX, corners[c].x);
|
||||
maxX = Mathf.Max(maxX, corners[c].x);
|
||||
minY = Mathf.Min(minY, corners[c].y);
|
||||
maxY = Mathf.Max(maxY, corners[c].y);
|
||||
}
|
||||
float z = corners[0].z;
|
||||
Vector3 center = new Vector3((minX + maxX) * 0.5f, (minY + maxY) * 0.5f, z);
|
||||
Vector3 size = new Vector3(Mathf.Max(0f, maxX - minX), Mathf.Max(0f, maxY - minY), 0f);
|
||||
b = new Bounds(center, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
b = movementBounds;
|
||||
}
|
||||
|
||||
if (completionClipEdgePadding > 0f)
|
||||
{
|
||||
Bounds shrunk = b;
|
||||
shrunk.Expand(-completionClipEdgePadding);
|
||||
if (shrunk.size.x >= 0.01f && shrunk.size.y >= 0.01f)
|
||||
{
|
||||
b = shrunk;
|
||||
}
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
private static Vector3 ClampPositionToBounds(Vector3 worldPos, Bounds b)
|
||||
{
|
||||
return new Vector3(
|
||||
Mathf.Clamp(worldPos.x, b.min.x, b.max.x),
|
||||
Mathf.Clamp(worldPos.y, b.min.y, b.max.y),
|
||||
worldPos.z
|
||||
);
|
||||
}
|
||||
|
||||
private void UpdateRenderers()
|
||||
{
|
||||
// 更新连接线渲染器
|
||||
@@ -833,7 +1093,7 @@ namespace AibisDream.MiniGame.Language
|
||||
focusTimer = 0f;
|
||||
fadeOutAlpha = 1f;
|
||||
allowInput = false;
|
||||
focusVisuals.Clear();
|
||||
DestroyFocusVisualDecorationsAndClear();
|
||||
finalTargetCharacters.Clear();
|
||||
finalTargetPositions.Clear();
|
||||
focusTweensCompleted = false;
|
||||
@@ -860,9 +1120,11 @@ namespace AibisDream.MiniGame.Language
|
||||
float totalWidth = spacing * Mathf.Max(0, finalTargetCharacters.Count - 1);
|
||||
float startX = -totalWidth / 2f;
|
||||
finalTargetPositions.Clear();
|
||||
Bounds clip = GetCompletionClipBounds();
|
||||
for (int i = 0; i < finalTargetCharacters.Count; i++)
|
||||
{
|
||||
finalTargetPositions.Add(displayCenter + new Vector3(startX + i * spacing, 0f, 0f));
|
||||
Vector3 pos = displayCenter + new Vector3(startX + i * spacing, 0f, 0f);
|
||||
finalTargetPositions.Add(ClampPositionToBounds(pos, clip));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -968,6 +1230,8 @@ namespace AibisDream.MiniGame.Language
|
||||
focusSequenceButton.interactable = false;
|
||||
}
|
||||
|
||||
ClearCompletionClampSmoothVelocity();
|
||||
|
||||
completionPhase = CompletionPhase.Focusing;
|
||||
focusTimer = 0f;
|
||||
focusTweensCompleted = false;
|
||||
@@ -1018,8 +1282,9 @@ namespace AibisDream.MiniGame.Language
|
||||
|
||||
focusCentroid = ComputeCentroid(orderedRedParticles);
|
||||
Vector3 focusTargetCenter = worldCanvas != null ? worldCanvas.transform.position : Vector3.zero;
|
||||
Bounds focusClipBounds = GetCompletionClipBounds();
|
||||
|
||||
focusVisuals.Clear();
|
||||
DestroyFocusVisualDecorationsAndClear();
|
||||
int tweenTargetCount = orderedRedParticles.Count;
|
||||
int completedTweens = 0;
|
||||
|
||||
@@ -1031,6 +1296,8 @@ namespace AibisDream.MiniGame.Language
|
||||
Vector3 spreadOffset = new Vector3((i - indexCenter) * spreadDistance, Mathf.Sin(i * 1.4f) * spreadDistance * 0.6f, 0f);
|
||||
Vector3 focusPosition = focusTargetCenter + offset * focusZoomScale + spreadOffset;
|
||||
Vector3 finalPosition = finalTargetPositions.Count > i ? finalTargetPositions[i] : focusTargetCenter;
|
||||
focusPosition = ClampPositionToBounds(focusPosition, focusClipBounds);
|
||||
finalPosition = ClampPositionToBounds(finalPosition, focusClipBounds);
|
||||
|
||||
particle.transform.DOKill();
|
||||
particle.transform.DOMove(focusPosition, focusMoveDuration)
|
||||
@@ -1213,29 +1480,35 @@ namespace AibisDream.MiniGame.Language
|
||||
}
|
||||
}
|
||||
|
||||
private void CompleteFocusSequence()
|
||||
private void DestroyFocusVisualDecorationsAndClear()
|
||||
{
|
||||
foreach (var visual in focusVisuals.Values)
|
||||
{
|
||||
if (visual == null)
|
||||
continue;
|
||||
if (visual.ringObject != null)
|
||||
{
|
||||
Destroy(visual.ringObject);
|
||||
}
|
||||
|
||||
if (visual.probabilityLabel != null)
|
||||
{
|
||||
Destroy(visual.probabilityLabel.gameObject);
|
||||
}
|
||||
|
||||
if (visual.particle != null)
|
||||
{
|
||||
visual.particle.transform.DOScale(Vector3.one * focusZoomScale, 0.3f).SetEase(Ease.OutQuad);
|
||||
visual.particle.isStatic = true;
|
||||
visual.particle.changeSpeedMultiplier = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
focusVisuals.Clear();
|
||||
}
|
||||
|
||||
private void CompleteFocusSequence()
|
||||
{
|
||||
var particlesToScale = new List<CandidateParticle>();
|
||||
foreach (var visual in focusVisuals.Values)
|
||||
{
|
||||
if (visual?.particle != null)
|
||||
particlesToScale.Add(visual.particle);
|
||||
}
|
||||
DestroyFocusVisualDecorationsAndClear();
|
||||
foreach (var p in particlesToScale)
|
||||
{
|
||||
p.transform.DOScale(Vector3.one * focusZoomScale, 0.3f).SetEase(Ease.OutQuad);
|
||||
p.isStatic = true;
|
||||
p.changeSpeedMultiplier = 1f;
|
||||
}
|
||||
|
||||
if (connectionRenderer != null)
|
||||
{
|
||||
@@ -1330,6 +1603,21 @@ namespace AibisDream.MiniGame.Language
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 立即隐藏火山释放 log(无动画),用于新一轮 start 前先淡出文字、再单独淡入 log。
|
||||
/// </summary>
|
||||
private void HideReleaseLogImmediate()
|
||||
{
|
||||
if (releaseLogSpriteRenderer == null)
|
||||
return;
|
||||
DOTween.Kill(releaseLogSpriteRenderer);
|
||||
releaseLogSpriteRenderer.enabled = false;
|
||||
releaseLogSpriteRenderer.gameObject.SetActive(false);
|
||||
Color c = releaseLogSpriteRenderer.color;
|
||||
c.a = 0f;
|
||||
releaseLogSpriteRenderer.color = c;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 若火山释放 log 界面已淡出,则淡入显示(start_expression 时调用,确保流程开始前界面可见)
|
||||
/// </summary>
|
||||
@@ -1441,7 +1729,7 @@ namespace AibisDream.MiniGame.Language
|
||||
fadeOutAlpha = 1f;
|
||||
allowInput = !playEntranceEffect;
|
||||
orderedRedParticles.Clear();
|
||||
focusVisuals.Clear();
|
||||
DestroyFocusVisualDecorationsAndClear();
|
||||
finalTargetCharacters.Clear();
|
||||
finalTargetPositions.Clear();
|
||||
previousConnections.Clear();
|
||||
@@ -1449,6 +1737,7 @@ namespace AibisDream.MiniGame.Language
|
||||
initializationFrames = 0;
|
||||
focusTweensCompleted = false;
|
||||
interferenceLogicSuspended = false;
|
||||
ClearCompletionClampSmoothVelocity();
|
||||
SetStatusUIVisibility(true, 0f);
|
||||
if (entranceRoutine != null)
|
||||
{
|
||||
|
||||
@@ -14,14 +14,32 @@ namespace AibisDream.MiniGame.Language
|
||||
private static AibisDream.ExpressionManager ExpressionManager => FixSystemCenter.SystemDic.Get<AibisDream.ExpressionManager>();
|
||||
|
||||
/// <summary>
|
||||
/// 启动表达粒子系统(在打开视图后调用),等待火山表达panel播完再返回
|
||||
/// 启动表达粒子系统(在打开视图后调用),等待火山表达panel播完再返回。
|
||||
/// 流程:若有上一轮则先淡出字与连线 → 再淡入火山释放 log → 再启动新一轮并等表达 panel 就绪。
|
||||
/// 注意:Yarn Spinner 2.x 对同名 <see cref="YarnCommand"/> 重载支持不可靠,只保留一个注册入口,用可选参数区分 3/4 参调用。
|
||||
/// <<start_expression "哈哈|嘿嘿|呵呵" "我很害怕" "ExpressionCompleted">>
|
||||
/// <<start_expression "哈哈|嘿嘿|呵呵" "我很害怕" "ExpressionCompleted" 24>>
|
||||
/// (第四项为「非目标」候选粒子总数,候选池 = 目标句字数 + 该值;不写第四项则用 Inspector 的 Candidate Count)
|
||||
/// </summary>
|
||||
/// <param name="anxietyPhrasesStr">干扰短句(笑话等),用 | 分隔,用于非目标粒子字符池,如:"哈哈|嘿嘿|呵呵"</param>
|
||||
/// <param name="targetSentence">目标句子</param>
|
||||
/// <param name="completionNode">完成时触发的对话节点(可选)</param>
|
||||
/// <param name="completionNode">完成时触发的对话节点(可空字符串)</param>
|
||||
/// <param name="nonTargetParticleTotal">非目标候选粒子总数;<0 表示使用 Inspector 默认</param>
|
||||
[YarnCommand("start_expression")]
|
||||
public static IEnumerator StartExpression(string anxietyPhrasesStr, string targetSentence, string completionNode = "")
|
||||
public static IEnumerator StartExpression(
|
||||
string anxietyPhrasesStr,
|
||||
string targetSentence,
|
||||
string completionNode = "",
|
||||
float nonTargetParticleTotal = -1f)
|
||||
{
|
||||
return RunStartExpression(
|
||||
anxietyPhrasesStr,
|
||||
targetSentence,
|
||||
completionNode ?? "",
|
||||
UnityEngine.Mathf.RoundToInt(nonTargetParticleTotal));
|
||||
}
|
||||
|
||||
private static IEnumerator RunStartExpression(string anxietyPhrasesStr, string targetSentence, string completionNode, int nonTargetParticleTotal)
|
||||
{
|
||||
if (ExpressionManager == null)
|
||||
{
|
||||
@@ -29,7 +47,6 @@ namespace AibisDream.MiniGame.Language
|
||||
yield break;
|
||||
}
|
||||
|
||||
// 解析干扰短句(用 | 分隔)
|
||||
string[] phrases = null;
|
||||
if (!string.IsNullOrEmpty(anxietyPhrasesStr))
|
||||
{
|
||||
@@ -40,9 +57,10 @@ namespace AibisDream.MiniGame.Language
|
||||
}
|
||||
}
|
||||
|
||||
// 若火山释放 log 界面已淡出,先淡入再开始流程
|
||||
// 先淡出上一轮文字/连线等,再淡入火山释放 log,最后开新一轮粒子(避免 log 与旧字叠在一起)
|
||||
yield return ExpressionManager.FadeOutParticlesBeforeNewRound(0f);
|
||||
yield return ExpressionManager.EnsureReleaseLogFadedIn(1f);
|
||||
ExpressionManager.StartSystem(phrases, targetSentence, completionNode);
|
||||
ExpressionManager.StartSystem(phrases, targetSentence, completionNode, nonTargetParticleTotal);
|
||||
yield return ExpressionManager.WaitUntilExpressionFlowReady();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user