diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionManager.cs b/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionManager.cs
index 4259c39de..448d3d035 100644
--- a/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionManager.cs
+++ b/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionManager.cs
@@ -100,7 +100,8 @@ namespace AibisDream
/// 干扰短句列表(笑话等,非目标粒子从中随机取字符)
/// 目标句子
/// 完成时触发的对话节点名称(可选)
- public void StartSystem(List anxietyPhrases, string targetSentence, string completionNodeName = null)
+ /// 非目标候选粒子总数;<0 时使用 Inspector 默认候选池大小
+ public void StartSystem(List 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);
}
}
///
/// 启动粒子系统(字符串数组版本,方便 Yarn 调用)
///
- public void StartSystem(string[] anxietyPhrases, string targetSentence, string completionNodeName = null)
+ public void StartSystem(string[] anxietyPhrases, string targetSentence, string completionNodeName = null, int nonTargetParticleTotal = -1)
{
List phrasesList = anxietyPhrases != null ? new List(anxietyPhrases) : null;
- StartSystem(phrasesList, targetSentence, completionNodeName);
+ StartSystem(phrasesList, targetSentence, completionNodeName, nonTargetParticleTotal);
}
///
@@ -131,6 +132,16 @@ namespace AibisDream
yield return particleManager.FadeInReleaseLogIfNeeded(duration);
}
+ ///
+ /// 再次 start_expression 前淡出上一轮粒子与连线(若尚未初始化则立即结束)。
+ /// duration≤0 时使用 LanguageParticleManager 上的默认时长。
+ ///
+ public IEnumerator FadeOutParticlesBeforeNewRound(float duration = 0f)
+ {
+ if (particleManager == null) yield break;
+ yield return StartCoroutine(particleManager.FadeOutBeforeNewExpressionRound(duration));
+ }
+
///
/// 等待表达流程就绪(开场表现 + 火山表达panel 播完)
///
diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleManager.cs b/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleManager.cs
index 707add50f..2821a6b6a 100644
--- a/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleManager.cs
+++ b/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleManager.cs
@@ -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 focusVisuals = new Dictionary();
+ /// 完成阶段屏幕钳制用 SmoothDamp 速度缓存(按粒子)
+ private readonly Dictionary completionClampSmoothVelocity = new Dictionary();
private Vector3 focusCentroid;
private List finalTargetCharacters = new List();
private List finalTargetPositions = new List();
@@ -236,6 +250,17 @@ namespace AibisDream.MiniGame.Language
// 是否已经初始化
private bool isInitialized = false;
+ /// Inspector 中的候选粒子总数,用于未在 Yarn 中指定非目标数量时恢复默认
+ 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
/// 焦虑短句列表
/// 目标句子
/// 完成时触发的对话节点
- public void InitializeSystem(List phrases, string sentence, string dialogNode = null)
+ ///
+ /// 非目标候选粒子总数(蓝/干扰侧可交互粒子数量)。≥0 时候选池大小 = 目标句字数 + 该值;<0 时使用 Inspector 的 Candidate Count。
+ ///
+ public void InitializeSystem(List 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();
- targetSentence = sentence ?? "这是一个测试示例";
+ targetSentence = useSentence;
completionDialogNode = dialogNode ?? "";
// 设置全局焦虑短句配置
TextParticle.SetAnxietyPhrases(anxietyPhrases);
// 根据目标句子更新目标粒子数量
- redParticleCount = Mathf.Min(sentence.Length, candidateCount);
+ redParticleCount = Mathf.Min(targetSentence.Length, candidateCount);
// 重新初始化游戏状态并播放入场动画
ResetGame(true);
}
+ ///
+ /// 在再次 start_expression 前调用:若系统已初始化过,则淡出当前粒子、连线与相关 UI,避免与新一轮重叠。
+ /// duration≤0 时使用 。
+ ///
+ 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;
+ }
+
///
/// 在打开视图前设置所有初始状态(panels、button 等不激活)
///
@@ -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();
- 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();
+ 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);
+ }
+
+ ///
+ /// 增删候选粒子以匹配目标数量(用于 Yarn 动态指定非目标粒子总数后复用同一视图)
+ ///
+ 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();
+ }
+
+ ///
+ /// 完成阶段使用的裁剪范围:优先用 Inspector 指定的屏幕 Rect,否则与粒子运动边界一致。
+ ///
+ 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();
+ 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
});
}
+ ///
+ /// 立即隐藏火山释放 log(无动画),用于新一轮 start 前先淡出文字、再单独淡入 log。
+ ///
+ 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;
+ }
+
///
/// 若火山释放 log 界面已淡出,则淡入显示(start_expression 时调用,确保流程开始前界面可见)
///
@@ -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)
{
diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/LanguageYarnCommand.cs b/Assets/Scripts/MiniGame/HuoShan/Language/LanguageYarnCommand.cs
index fcd26c6b1..8a913f5ca 100644
--- a/Assets/Scripts/MiniGame/HuoShan/Language/LanguageYarnCommand.cs
+++ b/Assets/Scripts/MiniGame/HuoShan/Language/LanguageYarnCommand.cs
@@ -14,14 +14,32 @@ namespace AibisDream.MiniGame.Language
private static AibisDream.ExpressionManager ExpressionManager => FixSystemCenter.SystemDic.Get();
///
- /// 启动表达粒子系统(在打开视图后调用),等待火山表达panel播完再返回
+ /// 启动表达粒子系统(在打开视图后调用),等待火山表达panel播完再返回。
+ /// 流程:若有上一轮则先淡出字与连线 → 再淡入火山释放 log → 再启动新一轮并等表达 panel 就绪。
+ /// 注意:Yarn Spinner 2.x 对同名 重载支持不可靠,只保留一个注册入口,用可选参数区分 3/4 参调用。
/// <>
+ /// <>
+ /// (第四项为「非目标」候选粒子总数,候选池 = 目标句字数 + 该值;不写第四项则用 Inspector 的 Candidate Count)
///
/// 干扰短句(笑话等),用 | 分隔,用于非目标粒子字符池,如:"哈哈|嘿嘿|呵呵"
/// 目标句子
- /// 完成时触发的对话节点(可选)
+ /// 完成时触发的对话节点(可空字符串)
+ /// 非目标候选粒子总数;<0 表示使用 Inspector 默认
[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();
}