using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; using AibisDream.FixSystem; using AibisDream; using AibisDream.Framework; using AibisDream.Utility; using UnityEngine.Localization; using UnityEngine.Localization.Settings; using Yarn.Unity; namespace AibisDream.MiniGame.Language { /// /// 表达系统的 Yarn 命令接口(原语言系统) /// public static class LanguageYarnCommand { private static AibisDream.ExpressionManager ExpressionManager => FixSystemCenter.SystemDic.Get(); private static Locale GetCommandLocale() { Locale current = LocalizationSettings.SelectedLocale; return ExpressionManager != null ? ExpressionManager.GetExpressionLocale(current) : current; } private static IEnumerator ResolveTextReferences( Locale locale, System.Action completed, params string[] references) { var tasks = new Task[references.Length]; for (int i = 0; i < references.Length; i++) tasks[i] = LocalizationKit.LocalizeParamAsync(references[i], locale); Task all = Task.WhenAll(tasks); while (!all.IsCompleted) yield return null; if (all.Status != TaskStatus.RanToCompletion) { completed?.Invoke(System.Array.Empty()); yield break; } string[] values = all.Result; for (int i = 0; i < values.Length; i++) { if (LocalizationKit.IsMissingParamResult(references[i], values[i])) values[i] = null; } completed?.Invoke(values); } private static bool ValidateRequiredText(string value, string command, string parameter) { if (!string.IsNullOrWhiteSpace(value)) return true; UnityEngine.Debug.LogError( $"[LanguageYarnCommand] {command} 的玩家可见参数 {parameter} 解析为空;命令已安全结束。"); return false; } public static bool TryParseExpressionTokens(string value, out List tokens) { tokens = new List(); if (string.IsNullOrWhiteSpace(value)) return false; if (value.Contains("|")) { UnityEngine.Debug.LogError( "[LanguageYarnCommand] 干扰 token 使用了全角分隔符“|”;请改用半角“|”。"); return false; } string[] parts = value.Split('|'); foreach (string part in parts) { string token = part.Trim(); if (!string.IsNullOrEmpty(token)) tokens.Add(token); } return tokens.Count > 0; } public static bool TryParseExpressionPool( string value, out List units) { return TryParseExpressionPool( value, ExpressionParticleUnitMode.Grapheme, out units); } public static bool TryParseExpressionPool( string value, ExpressionParticleUnitMode mode, out List units) { return ExpressionTextTokenizer.TryTokenizePool(value, mode, out units); } private static bool TryGetPresentation(out LogReleasePresentationController presentation) { presentation = ExpressionManager != null ? ExpressionManager.LogReleasePresentation : null; if (presentation != null) return true; UnityEngine.Debug.LogError( "[LanguageYarnCommand] LogReleasePresentationController 未找到;演出命令安全结束。"); return false; } /// /// 启动表达粒子系统(在打开视图后调用),等粒子字入场完成即可返回(可交互);panel 动效并行不阻塞。 /// 流程:若有上一轮则先淡出字与连线 → 再淡入火山释放 log → 再启动新一轮并等粒子就绪。 /// 注意:Yarn Spinner 2.x 对同名 重载支持不可靠,只保留一个注册入口,用可选参数区分 3/4 参调用。 /// <> /// <> /// (第四项为「非目标」候选粒子总数,候选池 = 目标句字数 + 该值;不写第四项则用 Inspector 的 Candidate Count) /// /// 干扰短句(笑话等),用 | 分隔,用于非目标粒子字符池,如:"哈哈|嘿嘿|呵呵" /// 目标句子 /// 完成时触发的对话节点(可空字符串) /// 非目标候选粒子总数;<0 表示使用 Inspector 默认 [YarnCommand("start_expression")] 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) { UnityEngine.Debug.LogError("ExpressionManager 未找到!请确保场景中有 ExpressionManager 组件。"); yield break; } Locale locale = LocalizationSettings.SelectedLocale; string[] localized = null; yield return ResolveTextReferences( locale, values => localized = values, anxietyPhrasesStr, targetSentence, ConstRef.ExpressParticlePoolParam); if (localized == null || localized.Length != 3 || !ValidateRequiredText(localized[0], "start_expression", nameof(anxietyPhrasesStr)) || !ValidateRequiredText(localized[1], "start_expression", nameof(targetSentence)) || !ValidateRequiredText(localized[2], "start_expression", "defaultCharacterPool")) yield break; if (!TryParseExpressionTokens(localized[0], out List phrases)) { UnityEngine.Debug.LogError( "[LanguageYarnCommand] start_expression 没有有效的干扰 token;命令已安全结束。"); yield break; } ExpressionParticleLocaleSettings profile = ExpressionManager.ParticleLanguageProfile != null ? ExpressionManager.ParticleLanguageProfile.Resolve(locale.Identifier) : ExpressionParticleLocaleSettings.CreateFallback(); if (!ExpressionRoundTextSnapshot.TryCreate( locale.Identifier, profile, localized[1], phrases, localized[2], out ExpressionRoundTextSnapshot snapshot, out string snapshotError)) { UnityEngine.Debug.LogError( $"[LanguageYarnCommand] start_expression 粒子单位解析失败:{snapshotError}"); yield break; } // 先清理上一轮演出,再淡出旧文字/连线并恢复 Log 操作界面。 yield return ExpressionManager.PreparePresentationForNextRound(); yield return ExpressionManager.FadeOutParticlesBeforeNewRound(0f); yield return ExpressionManager.EnsureReleaseLogFadedIn(1f); ExpressionManager.SetActiveExpressionLocale(locale); ExpressionManager.StartSystem( snapshot, completionNode, nonTargetParticleTotal); yield return ExpressionManager.WaitUntilExpressionFlowReady(); } /// /// 使用 ExpressionContentCatalog 中的轮次配置启动表达系统。 /// <> /// [YarnCommand("start_expression_round")] public static IEnumerator StartExpressionRound(string roundId, string completionNode = "") { if (ExpressionManager == null) { UnityEngine.Debug.LogError("ExpressionManager 未找到!请确保场景中有 ExpressionManager 组件。"); yield break; } ExpressionContentCatalog catalog = ExpressionManager.ContentCatalog; if (catalog == null) { UnityEngine.Debug.LogError( "[LanguageYarnCommand] ExpressionContentCatalog 未配置;无法启动表达轮次。"); yield break; } List catalogErrors = catalog.GetValidationErrors(); if (catalogErrors.Count > 0) { UnityEngine.Debug.LogError( "[LanguageYarnCommand] ExpressionContentCatalog 数据无效;无法启动表达轮次。\n" + string.Join("\n", catalogErrors)); yield break; } if (!catalog.TryGetRound(roundId, out ExpressionRoundDefinition round)) { UnityEngine.Debug.LogError( $"[LanguageYarnCommand] ExpressionContentCatalog 中不存在轮次 '{roundId}'。"); yield break; } Locale locale = LocalizationSettings.SelectedLocale; string[] localized = null; yield return ResolveTextReferences( locale, values => localized = values, round.TokenReference, round.TargetReference, ConstRef.ExpressParticlePoolParam); if (localized == null || localized.Length != 3 || !ValidateRequiredText(localized[0], "start_expression_round", nameof(round.TokenReference)) || !ValidateRequiredText(localized[1], "start_expression_round", nameof(round.TargetReference)) || !ValidateRequiredText(localized[2], "start_expression_round", "defaultCharacterPool")) yield break; if (!TryParseExpressionTokens(localized[0], out List phrases)) { UnityEngine.Debug.LogError( $"[LanguageYarnCommand] 表达轮次 '{roundId}' 没有有效的干扰 token。"); yield break; } ExpressionParticleLocaleSettings profile = ExpressionManager.ParticleLanguageProfile != null ? ExpressionManager.ParticleLanguageProfile.Resolve(locale.Identifier) : ExpressionParticleLocaleSettings.CreateFallback(); if (!ExpressionRoundTextSnapshot.TryCreate( locale.Identifier, profile, localized[1], phrases, localized[2], out ExpressionRoundTextSnapshot snapshot, out string snapshotError)) { UnityEngine.Debug.LogError( $"[LanguageYarnCommand] 表达轮次 '{roundId}' 粒子单位解析失败:{snapshotError}"); yield break; } yield return ExpressionManager.PreparePresentationForNextRound(); yield return ExpressionManager.FadeOutParticlesBeforeNewRound(0f); yield return ExpressionManager.EnsureReleaseLogFadedIn(1f); ExpressionManager.SetActiveExpressionLocale(locale); ExpressionManager.StartSystem( snapshot, completionNode ?? string.Empty, round.NonTargetParticleCount); yield return ExpressionManager.WaitUntilExpressionFlowReady(); } /// /// 关闭表达系统 /// <> /// [YarnCommand("close_expression")] public static void CloseExpression() { if (ExpressionManager == null) { UnityEngine.Debug.LogError("ExpressionManager 未找到!"); return; } ExpressionManager.CloseView(); } /// /// 重置表达系统(使用当前配置) /// <> /// 或 /// <> // 不播放入场动画 /// /// 是否播放入场效果 [YarnCommand("reset_expression")] public static void ResetExpression(bool playEntranceEffect = true) { if (ExpressionManager == null) { UnityEngine.Debug.LogError("ExpressionManager 未找到!"); return; } ExpressionManager.ResetSystem(playEntranceEffect); } /// /// 触发聚焦稳定化(从文字飘动保持态 → 概率递增 → 文字锁定 → 排列),等待全流程完成后返回 /// <> /// [YarnCommand("resolve_expression_focus")] public static IEnumerator ResolveExpressionFocus() { if (ExpressionManager == null) { UnityEngine.Debug.LogError("ExpressionManager 未找到!"); yield break; } yield return ExpressionManager.StartCoroutine(ExpressionManager.ResolveFocusAndWait()); } /// /// 淡入 ExpressionManager 的 Sprite 图片 /// <> /// <> /// /// 淡入时长(秒),默认 1 [YarnCommand("expression_sprite_fade_in")] public static IEnumerator ExpressionSpriteFadeIn(float duration = 1f) { if (ExpressionManager == null) { UnityEngine.Debug.LogError("ExpressionManager 未找到!"); yield break; } yield return ExpressionManager.StartCoroutine(ExpressionManager.FadeInSpriteOverlay(duration)); } /// /// 淡出 ExpressionManager 的 Sprite 图片 /// <> /// <> /// /// 淡出时长(秒),默认 1 [YarnCommand("expression_sprite_fade_out")] public static IEnumerator ExpressionSpriteFadeOut(float duration = 1f) { if (ExpressionManager == null) { UnityEngine.Debug.LogError("ExpressionManager 未找到!"); yield break; } yield return ExpressionManager.StartCoroutine(ExpressionManager.FadeOutSpriteOverlay(duration)); } /// /// Glitch 噪波过渡,等待完成后再继续下一步。 /// <> -- 从 0 到 0.95,2 秒 /// <> -- 立即到 0.95(duration 默认 0) /// <> -- 从 0.3 到 0.45,1 秒 /// [YarnCommand("glitch_transition")] public static IEnumerator GlitchTransition(float to, float duration = 0f) { if (ExpressionManager == null) { UnityEngine.Debug.LogError("ExpressionManager 未找到!"); yield break; } yield return ExpressionManager.StartCoroutine(ExpressionManager.TransitionGlitchAndWait(to, duration)); } /// /// 记忆淡入(非阻塞):透明度在 fadeDuration 内显现,画面同时按 pushSpeed 持续推近; /// targetOpacity 是本次淡入的目标透明度。horizontalSpeed 为旧 Yarn 兼容参数,基础层不再横移。 /// 进记忆前可用 glitch_transition 铺 noise;begin 时会把 noise 淡出,改由记忆材质 /// _SimGlitch / Analog 承接轻微失真。preserveGlitch 已忽略。争夺段 jolt/tear/impact 仍叠加。 /// [YarnCommand("expression_memory_begin")] public static void ExpressionMemoryBegin( string memoryKey, float fadeDuration = -1f, float pushSpeed = -1f, float horizontalSpeed = -1f, bool preserveGlitch = false, float targetOpacity = 1f) { if (ExpressionManager == null) { UnityEngine.Debug.LogError("ExpressionManager 未找到!"); return; } ExpressionManager.StartCoroutine( ExpressionManager.BeginExpressionMemory( memoryKey, fadeDuration, pushSpeed, horizontalSpeed, preserveGlitch, targetOpacity)); } /// /// 随对白把当前记忆透明度推进到目标值(非阻塞)。 /// <> /// [YarnCommand("expression_memory_alpha")] public static void ExpressionMemoryAlpha(float target, float duration = 0.4f) { if (TryGetPresentation(out LogReleasePresentationController presentation)) presentation.SetActiveMemoryOpacity(target, duration); } /// /// 显式淡入火山脸,并等待淡入完成。 /// <> /// [YarnCommand("expression_face_fade_in")] public static IEnumerator ExpressionFaceFadeIn(float duration = 0.45f) { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; yield return ExpressionManager.StartCoroutine( presentation.BeginFaceEntry(duration)); } // 以下命令是一动作一命令。非 IEnumerator 命令只启动效果,节拍由 Yarn 的 wait 控制。 [YarnCommand("expression_lie_prepare")] public static void ExpressionLiePrepare(float freezeDuration, float noiseTarget) { if (TryGetPresentation(out LogReleasePresentationController presentation)) presentation.PrepareLieVisuals(freezeDuration, noiseTarget); } [YarnCommand("expression_truth_squeeze")] public static void ExpressionTruthSqueeze(float duration, float jitter, float alpha) { if (TryGetPresentation(out LogReleasePresentationController presentation)) presentation.SqueezeTruthCharacters(duration, jitter, alpha); } [YarnCommand("expression_lie_crack_begin")] public static void ExpressionLieCrackBegin(float lieAlpha) { if (TryGetPresentation(out LogReleasePresentationController presentation)) presentation.BeginLieCracking(lieAlpha); } [YarnCommand("expression_screen_dim")] public static void ExpressionScreenDim(float alpha, float riseDuration, float fallDuration) { if (TryGetPresentation(out LogReleasePresentationController presentation)) presentation.PlayScreenDim(alpha, riseDuration, fallDuration); } [YarnCommand("expression_memory_jolt")] public static void ExpressionMemoryJolt(float offset, float duration, float tear) { if (TryGetPresentation(out LogReleasePresentationController presentation)) presentation.PlayMemoryJolt(offset, duration, tear); } /// /// 调节记忆的轻微失真扰动(非阻塞)。默认强度由演出预设决定,本命令用于演出中微调。 /// <> /// [YarnCommand("expression_memory_sim")] public static void ExpressionMemorySim(float target, float duration = 0.3f) { if (TryGetPresentation(out LogReleasePresentationController presentation)) presentation.SetSimulationGlitch(target, duration); } /// /// 真话聚合尝试(非阻塞):目标字被慢慢拉向最终位置并按字序逐个锁定成真心话。 /// target 小于 1 时最后几个字永远差一点锁不住——"感觉快要成功了"。 /// <> /// [YarnCommand("expression_truth_gather")] public static void ExpressionTruthGather(float target = 0.9f, float duration = 3f) { if (TryGetPresentation(out LogReleasePresentationController presentation)) presentation.BeginTruthGatherAttempt(target, duration); } /// /// 真话聚合被打断(非阻塞):目标字向外爆散并恢复乱跳。burst 为世界单位的爆散距离。 /// <> /// [YarnCommand("expression_truth_scatter")] public static void ExpressionTruthScatter(float burst = 0.9f) { if (TryGetPresentation(out LogReleasePresentationController presentation)) presentation.ScatterTruthGatherAttempt(burst); } /// /// 真话聚合挣扎(非阻塞):向 target 推进的途中反复"够到一点又被拽回去", /// 已锁的字会崩开重新乱跳。挣扎感来自聚合曲线本身,不使用任何颜色或额外特效。 /// <> /// /// 最终停留的聚合进度 /// 整段时长 /// 回抽次数(0~4);<=0 时退化为 expression_truth_gather [YarnCommand("expression_truth_strain")] public static void ExpressionTruthStrain(float target = 0.85f, float duration = 3f, float tugs = 2f) { if (TryGetPresentation(out LogReleasePresentationController presentation)) { presentation.BeginTruthStrainAttempt( target, duration, UnityEngine.Mathf.RoundToInt(tugs)); } } /// /// 真话聚合崩塌(非阻塞):进度被一口气拽回 0,字沿原路退回悬浮位并恢复乱跳。 /// 与 expression_truth_scatter 的区别:scatter 是径向爆散,collapse 是"被拽回去"。 /// <> /// [YarnCommand("expression_truth_collapse")] public static void ExpressionTruthCollapse(float duration = 0.45f) { if (TryGetPresentation(out LogReleasePresentationController presentation)) presentation.CollapseTruthGatherAttempt(duration); } /// /// 火山脸错误闪红(非阻塞):聚合失败时短暂替换红屏 Sprite,随后恢复,并轻微下沉。 /// 注意:LOG1/LOG2 的失败信号已明确不使用红色与脸部报错,此命令仅保留兼容。 /// <> /// [YarnCommand("expression_face_error")] public static void ExpressionFaceError(float duration = 0.5f, float intensity = 0.85f) { if (TryGetPresentation(out LogReleasePresentationController presentation)) presentation.PlayFaceErrorFlash(duration, intensity); } /// /// 结尾真假快速闪切(非阻塞,节奏由同长度 wait 控制):真心话与谎话交替独占屏幕, /// 两者的屏幕底色(电视头背景)不同,越到后面切得越快,结束定格在真话帧。 /// <> /// [YarnCommand("expression_truth_lie_flicker")] public static IEnumerator ExpressionTruthLieFlicker( string liePhrases, string truthPhrase, float duration, float interval = 0.24f, float fontSize = 12f) { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; string[] localized = null; yield return ResolveTextReferences( GetCommandLocale(), values => localized = values, liePhrases, truthPhrase); if (localized == null || localized.Length != 2 || !ValidateRequiredText(localized[0], "expression_truth_lie_flicker", nameof(liePhrases)) || !ValidateRequiredText(localized[1], "expression_truth_lie_flicker", nameof(truthPhrase))) yield break; presentation.StartTruthLieFlicker(localized[0], localized[1], duration, interval, fontSize); } /// /// 定格的谎话被 glitch 翻转成真话(阻塞):大字乱跳中长度从谎话渐变到真话、真假底色抢闪, /// 最后落在暖色真话。紧接 expression_memory_cut + expression_truth_snap 收尾。 /// <> /// [YarnCommand("expression_lie_morph_truth")] public static IEnumerator ExpressionLieMorphTruth( string lieText, string truthText, float duration = 0.9f) { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; string[] localized = null; yield return ResolveTextReferences( GetCommandLocale(), values => localized = values, lieText, truthText); if (localized == null || localized.Length != 2 || !ValidateRequiredText(localized[0], "expression_lie_morph_truth", nameof(lieText)) || !ValidateRequiredText(localized[1], "expression_lie_morph_truth", nameof(truthText))) yield break; yield return ExpressionManager.StartCoroutine( presentation.MorphLieToTruth(localized[0], localized[1], duration)); } [YarnCommand("expression_truth_leak")] public static IEnumerator ExpressionTruthLeak(string fragment, float duration) { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; string[] localized = null; yield return ResolveTextReferences( GetCommandLocale(), values => localized = values, fragment); if (localized == null || localized.Length != 1 || !ValidateRequiredText(localized[0], "expression_truth_leak", nameof(fragment))) yield break; yield return ExpressionManager.StartCoroutine( presentation.LeakTruthFragment(localized[0], duration)); } [YarnCommand("expression_lie_shuffle")] public static IEnumerator ExpressionLieShuffle( string characterPool, float duration, float interval) { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; string[] localized = null; yield return ResolveTextReferences( GetCommandLocale(), values => localized = values, characterPool); if (localized == null || localized.Length != 1 || !ValidateRequiredText(localized[0], "expression_lie_shuffle", nameof(characterPool))) yield break; presentation.StartLieCharacterShuffle(localized[0], duration, interval); } /// /// 演员大字闪现(阻塞):抖动的粒子字瞬间隐藏,屏幕中央出现白色大字(红青色散双影+急速缩放), /// 停顿 holdDuration 秒后始终恢复粒子字抖动;结尾定格请使用 expression_actor_flash_in。 /// <> /// [YarnCommand("expression_actor_flash")] public static IEnumerator ExpressionActorFlash( string text, float holdDuration = 0.6f, float fontSize = 10.5f, float settledScale = -1f, float punchScale = -1f, float chromaDistance = -1f, float chromaAlpha = -1f, float impact = -1f, float glitchPeak = -1f) { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; string[] localized = null; yield return ResolveTextReferences( GetCommandLocale(), values => localized = values, text); if (localized == null || localized.Length != 1 || !ValidateRequiredText(localized[0], "expression_actor_flash", nameof(text))) yield break; yield return ExpressionManager.StartCoroutine( presentation.FlashActorLie( localized[0], holdDuration, fontSize, settledScale, punchScale, chromaDistance, chromaAlpha, impact, glitchPeak)); } /// /// 演员大字入场并保持(阻塞):入场表现与 expression_actor_flash 相同,但不执行退场, /// 适合在段落结尾定格。后续清场命令仍会正常移除它。 /// <> /// [YarnCommand("expression_actor_flash_in")] public static IEnumerator ExpressionActorFlashIn( string text, float holdDuration = 0.35f, float fontSize = 10.5f, float settledScale = -1f, float punchScale = -1f, float chromaDistance = -1f, float chromaAlpha = -1f, float impact = -1f, float glitchPeak = -1f) { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; string[] localized = null; yield return ResolveTextReferences( GetCommandLocale(), values => localized = values, text); if (localized == null || localized.Length != 1 || !ValidateRequiredText(localized[0], "expression_actor_flash_in", nameof(text))) yield break; yield return ExpressionManager.StartCoroutine( presentation.FlashActorLie( localized[0], holdDuration, fontSize, settledScale, punchScale, chromaDistance, chromaAlpha, impact, glitchPeak, true)); } /// /// 真话闪现(阻塞):谎话大字之间,暖色完整真话带细微抖动挣扎浮现一拍; /// "被掐断"由紧随其后的 expression_glitch_pulse 表现。不推进谎话冲击等级。 /// <> /// [YarnCommand("expression_actor_truth_flash")] public static IEnumerator ExpressionActorTruthFlash( string text, float holdDuration = 0.35f, float fontSize = 8.5f, float alpha = 0.8f, float jitter = 0.02f) { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; string[] localized = null; yield return ResolveTextReferences( GetCommandLocale(), values => localized = values, text); if (localized == null || localized.Length != 1 || !ValidateRequiredText(localized[0], "expression_actor_truth_flash", nameof(text))) yield break; yield return ExpressionManager.StartCoroutine( presentation.FlashActorTruth(localized[0], holdDuration, fontSize, alpha, jitter)); } /// /// 老虎机换字(非阻塞,节拍由 Yarn wait 控制):聚焦字在 duration 内只从 charPool 里疯狂换字。 /// truthPool 非空时其字符也混入换字池,并以真话暖色显示。 /// <> /// [YarnCommand("expression_actor_slot")] public static IEnumerator ExpressionActorSlot( string charPool, float duration, float interval = 0.05f, float fontSize = 14f, string truthPool = "") { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; string[] localized = null; yield return ResolveTextReferences( GetCommandLocale(), values => localized = values, charPool, truthPool); if (localized == null || localized.Length != 2 || !ValidateRequiredText(localized[0], "expression_actor_slot", nameof(charPool))) yield break; presentation.StartActorSlotMachine( localized[0], duration, interval, fontSize, localized[1] ?? string.Empty); } /// /// 将 Glitch、记忆冲击与画面推近缓慢堆到峰值,停住后保持峰值,等待后续硬切。 /// <> /// [YarnCommand("expression_overload_peak")] public static IEnumerator ExpressionOverloadPeak( float riseDuration = 0.75f, float holdDuration = 0.22f, float zoom = 1.10f, float glitchPeak = 1f, float impact = 1f) { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; yield return ExpressionManager.StartCoroutine( presentation.BuildActorOverloadPeak( riseDuration, holdDuration, zoom, glitchPeak, impact)); } /// /// 单字/短词满屏重击,并等待该拍结束。 /// <> /// [YarnCommand("expression_actor_word_hit")] public static IEnumerator ExpressionActorWordHit( string text, float holdDuration = 0.16f, float fontSize = 18f, float punchScale = 1.55f) { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; string[] localized = null; yield return ResolveTextReferences( GetCommandLocale(), values => localized = values, text); if (localized == null || localized.Length != 1 || !ValidateRequiredText(localized[0], "expression_actor_word_hit", nameof(text))) yield break; yield return ExpressionManager.StartCoroutine( presentation.PlayActorWordHit(localized[0], holdDuration, fontSize, punchScale)); } /// /// 单列词组严格裁在表情屏内,像字幕一样无缝向下滚动。 /// <> /// [YarnCommand("expression_actor_scroll")] public static IEnumerator ExpressionActorScroll( string phraseList, float scrollDuration = 1.8f, float loopDuration = 0.85f, float holdDuration = 0.45f, float fontSize = 5.2f) { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; string[] localized = null; yield return ResolveTextReferences( GetCommandLocale(), values => localized = values, phraseList); if (localized == null || localized.Length != 1 || !ValidateRequiredText(localized[0], "expression_actor_scroll", nameof(phraseList))) yield break; yield return ExpressionManager.StartCoroutine( presentation.ScrollActorLies( localized[0], scrollDuration, loopDuration, holdDuration, fontSize)); } [YarnCommand("expression_memory_cut")] public static void ExpressionMemoryCut() { if (TryGetPresentation(out LogReleasePresentationController presentation)) presentation.CutMemoryAndGlitchImmediate(); } [YarnCommand("expression_truth_snap")] public static void ExpressionTruthSnap(float peripheral, float calmRadius) { if (TryGetPresentation(out LogReleasePresentationController presentation)) presentation.SnapTruthImmediate(peripheral, calmRadius); } [YarnCommand("expression_glitch_pulse")] public static void ExpressionGlitchPulse(float peak, float riseDuration, float fallDuration) { if (TryGetPresentation(out LogReleasePresentationController presentation)) presentation.PlayGlitchPulse(peak, riseDuration, fallDuration); } /// /// 火山「屏幕」绝对档位(非阻塞)。作用在屏幕材质,不是叠一层噪波。 /// 会清掉当前脉冲并把底压直接设成 ;到位后一直保持。 /// 逐句积蓄情绪请改用 expression_screen_stress / expression_screen_pulse, /// 这条命令保留给"我就是要现在正好这个档位"的场合。 /// <> … <> /// [YarnCommand("expression_screen_glitch")] public static void ExpressionScreenGlitch( float level = 0.5f, float duration = 1f) { if (ExpressionManager == null) { UnityEngine.Debug.LogError( "[LanguageYarnCommand] expression_screen_glitch 找不到 ExpressionManager;已安全跳过。"); return; } ExpressionManager.SetScreenGlitchLevel(level, duration); } /// /// 屏幕蓄压(非阻塞):往底压罐里加一口,只升不降,这就是"积蓄的情绪"。 /// 每次情绪推进加一次,不用记上次是多少档;有软上限,堆不满。 /// <> /// /// 这次加多少(建议 0.08~0.2) /// 爬升到新底压的时间 [YarnCommand("expression_screen_stress")] public static void ExpressionScreenStress(float amount = 0.15f, float duration = 0.8f) { if (ExpressionManager == null) { UnityEngine.Debug.LogError( "[LanguageYarnCommand] expression_screen_stress 找不到 ExpressionManager;已安全跳过。"); return; } ExpressionManager.AddScreenStress(amount, duration); } /// /// 屏幕冲击(非阻塞):瞬间顶到「底压 + strength」,随后自动落回底压。 /// 一句狠话打一记;底压很低的开场也能打得很响,打完自己回到安静。 /// <> /// /// 冲击高度(建议 0.3~0.8) /// 落回底压的时间 [YarnCommand("expression_screen_pulse")] public static void ExpressionScreenPulse(float strength = 0.5f, float decay = 1.6f) { if (ExpressionManager == null) { UnityEngine.Debug.LogError( "[LanguageYarnCommand] expression_screen_pulse 找不到 ExpressionManager;已安全跳过。"); return; } ExpressionManager.PulseScreenGlitch(strength, decay); } /// /// 屏幕转红(非阻塞):荧光色从青绿推向愤怒红,LOG3 攻击段用。 /// 只换颜色不改故障强度,配合 expression_screen_pulse 打 punch。 /// <> … <> /// [YarnCommand("expression_screen_rage")] public static void ExpressionScreenRage(float level = 1f, float duration = 0.5f) { if (ExpressionManager == null) { UnityEngine.Debug.LogError( "[LanguageYarnCommand] expression_screen_rage 找不到 ExpressionManager;已安全跳过。"); return; } ExpressionManager.SetScreenRage(level, duration); } /// /// 屏幕泄压(非阻塞):底压和脉冲一起放掉归零,情绪落幕时用。 /// <> /// [YarnCommand("expression_screen_release")] public static void ExpressionScreenRelease(float duration = 1.2f) { if (ExpressionManager == null) { UnityEngine.Debug.LogError( "[LanguageYarnCommand] expression_screen_release 找不到 ExpressionManager;已安全跳过。"); return; } ExpressionManager.ReleaseScreenStress(duration); } /// /// 用现有目标粒子完整显示一个红色攻击词,并等待确定性的冲击动作结束。 /// 超过目标粒子数量时会安全跳过,不会截断。 /// <> /// [YarnCommand("expression_truth_attack")] public static IEnumerator ExpressionTruthAttack( string text, float duration = 0.34f, float impact = 0.5f) { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; string[] localized = null; yield return ResolveTextReferences( GetCommandLocale(), values => localized = values, text); if (localized == null || localized.Length != 1 || !ValidateRequiredText(localized[0], "expression_truth_attack", nameof(text))) yield break; // 红屏武装时每个攻击词都把屏幕顶一下(punch),力度跟着这个词的 impact 走。 // 写在这里而不是 Yarn 里逐条加 pulse:攻击列表经常重调,两边手写必然脱节。 ExpressionManager.PunchScreenForAttack(impact); yield return ExpressionManager.StartCoroutine( presentation.PlayTruthAttack(localized[0], duration, impact)); } /// /// Keep an already resolved truth phrase in place while it gradually becomes unstable and red. /// <> /// [YarnCommand("expression_truth_destabilize")] public static IEnumerator ExpressionTruthDestabilize( string text, float duration = 1.6f, float shakeAmplitude = 0.13f, float shakeSpeed = 22f, string colorHex = "FF304D") { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; string[] localized = null; yield return ResolveTextReferences( GetCommandLocale(), values => localized = values, text); if (localized == null || localized.Length != 1 || !ValidateRequiredText(localized[0], "expression_truth_destabilize", nameof(text))) yield break; yield return ExpressionManager.StartCoroutine( presentation.DestabilizeTruthPhrase( localized[0], duration, shakeAmplitude, shakeSpeed, colorHex)); } /// /// 将当前攻击词重组为固定青绿色真话,并等待稳定完成。 /// <> /// [YarnCommand("expression_truth_settle")] public static IEnumerator ExpressionTruthSettle( string text, float duration = 0.8f) { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; string[] localized = null; yield return ResolveTextReferences( GetCommandLocale(), values => localized = values, text); if (localized == null || localized.Length != 1 || !ValidateRequiredText(localized[0], "expression_truth_settle", nameof(text))) yield break; yield return ExpressionManager.StartCoroutine( presentation.SettleTruthPhrase(localized[0], duration)); } [YarnCommand("expression_truth_blackout")] public static IEnumerator ExpressionTruthBlackout(float duration, float alpha) { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; yield return ExpressionManager.StartCoroutine( presentation.HoldTruthBlackout(duration, alpha)); } [YarnCommand("expression_lie_split")] public static void ExpressionLieSplit( float duration, float distanceRatio, float stretchX, float squashY) { if (TryGetPresentation(out LogReleasePresentationController presentation)) { presentation.SplitLieVisual( duration, distanceRatio, stretchX, squashY); } } [YarnCommand("expression_truth_flash")] public static void ExpressionTruthFlash( float alpha, float riseDuration, float holdDuration, float fallDuration) { if (TryGetPresentation(out LogReleasePresentationController presentation)) { presentation.PlayTruthFlash( alpha, riseDuration, holdDuration, fallDuration); } } [YarnCommand("expression_memory_brightness_pulse")] public static void ExpressionMemoryBrightnessPulse( float amount, float riseDuration, float fallDuration) { if (TryGetPresentation(out LogReleasePresentationController presentation)) presentation.PlayMemoryBrightnessPulse(amount, riseDuration, fallDuration); } [YarnCommand("expression_memory_tear")] public static void ExpressionMemoryTear(float peak, float duration) { if (TryGetPresentation(out LogReleasePresentationController presentation)) presentation.PlayMemoryTear(peak, duration); } [YarnCommand("expression_face_dip")] public static void ExpressionFaceDip(float duration, float ratio) { if (TryGetPresentation(out LogReleasePresentationController presentation)) presentation.PlayFaceDip(duration, ratio); } [YarnCommand("expression_truth_release")] public static void ExpressionTruthRelease(float alphaDuration) { if (TryGetPresentation(out LogReleasePresentationController presentation)) presentation.ReleaseTruthCharacters(alphaDuration); } [YarnCommand("expression_truth_resolve")] public static IEnumerator ExpressionTruthResolve( float duration, float peripheral, float calmRadius) { if (!TryGetPresentation(out LogReleasePresentationController presentation)) yield break; yield return ExpressionManager.StartCoroutine( presentation.ResolveTruthCharacters(duration, peripheral, calmRadius)); } [YarnCommand("expression_memory_end")] public static IEnumerator ExpressionMemoryEnd() { if (ExpressionManager == null) { UnityEngine.Debug.LogError("ExpressionManager 未找到!"); yield break; } yield return ExpressionManager.StartCoroutine(ExpressionManager.EndExpressionMemory()); } [YarnCommand("expression_truth_impact")] public static void ExpressionTruthImpact() { if (ExpressionManager == null) { UnityEngine.Debug.LogError("ExpressionManager 未找到!"); return; } ExpressionManager.ImpactExpressionTruth(); } // /// // /// Glitch 噪波过渡(指定起止值),等待完成后再继续下一步。 // /// // [YarnCommand("glitch_transition")] // public static IEnumerator GlitchTransition(float from, float to, float duration) // { // if (ExpressionManager == null) // { // UnityEngine.Debug.LogError("ExpressionManager 未找到!"); // yield break; // } // yield return ExpressionManager.StartCoroutine(ExpressionManager.TransitionGlitchAndWait(from, to, duration)); // } } }