From f67641407d167e63edccb6747a2c311dd8ff84eb Mon Sep 17 00:00:00 2001 From: bottlefish <781230111@qq.com> Date: Fri, 27 Mar 2026 14:26:42 +0800 Subject: [PATCH] =?UTF-8?q?fix(huoshan):=20=E4=BF=AE=E5=A4=8D=E5=A4=9A?= =?UTF-8?q?=E8=BD=AE=E8=A1=A8=E8=BE=BE=E9=87=8D=E5=8F=A0=E5=B9=B6=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E9=9D=9E=E7=9B=AE=E6=A0=87=E7=B2=92=E5=AD=90=E6=95=B0?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made-with: Cursor --- .../HuoShan/Language/ExpressionManager.cs | 19 +- .../Language/LanguageParticleManager.cs | 365 ++++++++++++++++-- .../HuoShan/Language/LanguageYarnCommand.cs | 30 +- 3 files changed, 366 insertions(+), 48 deletions(-) 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(); }