feat(huoshan): 扩展火山语言系统与表情管理

This commit is contained in:
2026-03-13 22:07:52 +08:00
parent c2f4c0f83b
commit e89f8dbe23
4 changed files with 129 additions and 73 deletions
@@ -31,6 +31,10 @@ namespace AibisDream
[Header("Sprite 淡入淡出")]
[SerializeField] private SpriteRenderer spriteRenderer;
[Header("Glitch 噪波效果")]
[Tooltip("噪波故障效果控制器,可选;置于 Expression View 子物体上")]
[SerializeField] private SpriteNoiseGlitchController glitchController;
void Start()
{
// 注册到系统字典
@@ -53,6 +57,11 @@ namespace AibisDream
particleManager = GetComponentInChildren<LanguageParticleManager>(true);
}
if (glitchController == null)
{
glitchController = GetComponentInChildren<SpriteNoiseGlitchController>(true);
}
// 注册相机
var virtualCam = transform.Find("Expression Camera")?.GetComponent<ICinemachineCamera>();
if (virtualCam != null)
@@ -112,6 +121,16 @@ namespace AibisDream
StartSystem(phrasesList, targetSentence, completionNodeName);
}
/// <summary>
/// 若火山释放 log 界面已淡出,则先淡入再返回(start_expression 时调用)
/// </summary>
/// <param name="duration">淡入时长(秒)</param>
public IEnumerator EnsureReleaseLogFadedIn(float duration = 1f)
{
if (particleManager == null) yield break;
yield return particleManager.FadeInReleaseLogIfNeeded(duration);
}
/// <summary>
/// 等待表达流程就绪(开场表现 + 火山表达panel 播完)
/// </summary>
@@ -194,6 +213,33 @@ namespace AibisDream
spriteRenderer.gameObject.SetActive(false);
}
/// <summary>
/// Glitch 噪波过渡:从当前强度平滑过渡到 to,不瞬移(类似 DOTween)。
/// </summary>
public void TransitionGlitch(float to, float duration = 0f)
{
if (glitchController == null) return;
glitchController.TransitionTo(to, duration);
}
/// <summary>
/// Glitch 噪波过渡并等待完成:从当前强度平滑过渡到 to。
/// </summary>
public IEnumerator TransitionGlitchAndWait(float to, float duration = 0f)
{
if (glitchController == null) yield break;
yield return glitchController.StartCoroutine(glitchController.TransitionToAndWait(to, duration));
}
/// <summary>
/// Glitch 噪波过渡并等待完成:从当前强度平滑过渡到 to(from 忽略,保持丝滑连续)。
/// </summary>
public IEnumerator TransitionGlitchAndWait(float from, float to, float duration)
{
if (glitchController == null) yield break;
yield return glitchController.StartCoroutine(glitchController.TransitionToAndWait(to, duration));
}
private void OnDestroy()
{
CameraKit.Instance?.UnRegisterCamera(CameraEnum.Expression);
@@ -117,7 +117,7 @@ namespace AibisDream.MiniGame.Language
[SerializeField] private GameObject languageDeepPanel1;
[SerializeField] private GameObject languageDeepPanel2;
[SerializeField] private Button focusSequenceButton;
[Tooltip("火山释放log界面,结束时会对其 SpriteRenderer 做淡出")]
[Tooltip("火山释放log界面,触发 focus 时对其 SpriteRenderer 做淡出,重新开始时恢复")]
[SerializeField] private SpriteRenderer releaseLogSpriteRenderer;
[Tooltip("开场表达panel Timeline 名称(DirectorName 或 DirectorName/AddressableKey),播完才算表达流程完成")]
[SerializeField] private string expressionPanelTimelineName = "火山表达模块/火山表达panel";
@@ -974,6 +974,15 @@ namespace AibisDream.MiniGame.Language
interferenceLogicSuspended = true;
SetStatusUIVisibility(false, statusFadeOutDuration);
// 两个 panel、confirm button、火山释放 log 界面在 focus 触发时淡出
FadeOutUIElement(languageDeepPanel1, focusFadeDuration);
FadeOutUIElement(languageDeepPanel2, focusFadeDuration);
if (focusSequenceButton != null)
{
FadeOutUIElement(focusSequenceButton.gameObject, focusFadeDuration);
}
FadeOutSpriteRenderer(releaseLogSpriteRenderer, focusFadeDuration);
if (connectionRenderer != null)
{
connectionRenderer.NonTargetAlphaScale = 0f;
@@ -1234,15 +1243,6 @@ namespace AibisDream.MiniGame.Language
}
completionPhase = CompletionPhase.Finished;
// languageDeepPanel1、languageDeepPanel2、button、火山释放log界面 一起淡出
FadeOutUIElement(languageDeepPanel1, focusFadeDuration);
FadeOutUIElement(languageDeepPanel2, focusFadeDuration);
if (focusSequenceButton != null)
{
FadeOutUIElement(focusSequenceButton.gameObject, focusFadeDuration);
}
FadeOutSpriteRenderer(releaseLogSpriteRenderer, focusFadeDuration);
}
private Vector3 GetDynamicArrangementTarget(TargetFocusVisual visual, float arrangementProgress, float localTime)
@@ -1330,6 +1330,39 @@ namespace AibisDream.MiniGame.Language
});
}
/// <summary>
/// 若火山释放 log 界面已淡出,则淡入显示(start_expression 时调用,确保流程开始前界面可见)
/// </summary>
/// <param name="duration">淡入时长(秒)</param>
public IEnumerator FadeInReleaseLogIfNeeded(float duration = 1f)
{
if (releaseLogSpriteRenderer == null) yield break;
if (releaseLogSpriteRenderer.gameObject.activeSelf && releaseLogSpriteRenderer.color.a > 0.9f)
yield break; // 已显示,跳过
DOTween.Kill(releaseLogSpriteRenderer);
releaseLogSpriteRenderer.enabled = true;
releaseLogSpriteRenderer.gameObject.SetActive(true);
Color c = releaseLogSpriteRenderer.color;
c.a = 0f;
releaseLogSpriteRenderer.color = c;
yield return releaseLogSpriteRenderer.DOFade(1f, duration).SetEase(Ease.OutQuad).WaitForCompletion();
}
/// <summary>
/// 恢复 SpriteRenderer 显示(用于重新开始时恢复火山释放 log 界面)
/// </summary>
private void RestoreSpriteRenderer(SpriteRenderer sr)
{
if (sr == null) return;
DOTween.Kill(sr);
sr.enabled = true;
sr.gameObject.SetActive(true);
Color c = sr.color;
c.a = 1f;
sr.color = c;
}
private Vector3 ComputeCentroid(List<CandidateParticle> particles)
{
if (particles == null || particles.Count == 0)
@@ -1448,6 +1481,9 @@ namespace AibisDream.MiniGame.Language
if (languageDeepPanel2 != null)
languageDeepPanel2.SetActive(false);
// 恢复火山释放 log 界面 SpriteRenderer(重新开始时恢复)
RestoreSpriteRenderer(releaseLogSpriteRenderer);
if (connectionRenderer != null)
{
connectionRenderer.NonTargetAlphaScale = lineAlphaMultiplier;
@@ -40,6 +40,8 @@ namespace AibisDream.MiniGame.Language
}
}
// 若火山释放 log 界面已淡出,先淡入再开始流程
yield return ExpressionManager.EnsureReleaseLogFadedIn(1f);
ExpressionManager.StartSystem(phrases, targetSentence, completionNode);
yield return ExpressionManager.WaitUntilExpressionFlowReady();
}
@@ -94,46 +96,6 @@ namespace AibisDream.MiniGame.Language
yield return ExpressionManager.StartCoroutine(ExpressionManager.ResolveFocusAndWait());
}
/// <summary>
/// 重新启动表达系统(会重置并使用新配置,相当于 close + open + start
/// <<restart_expression "我好怕|怎么办|不行" "我能做到" "ExpressionCompleted">>
/// </summary>
/// <param name="anxietyPhrasesStr">干扰短句(笑话等),用 | 分隔</param>
/// <param name="targetSentence">目标句子</param>
/// <param name="completionNode">完成时触发的对话节点(可选)</param>
[YarnCommand("restart_expression")]
public static void RestartExpression(string anxietyPhrasesStr, string targetSentence, string completionNode = "")
{
if (ExpressionManager == null)
{
UnityEngine.Debug.LogError("ExpressionManager 未找到!");
return;
}
// 停止当前系统
ExpressionManager.CloseView();
// 等一帧后重新启动
UnityEngine.MonoBehaviour coroutineRunner = UnityEngine.Object.FindObjectOfType<UnityEngine.MonoBehaviour>();
if (coroutineRunner != null)
{
coroutineRunner.StartCoroutine(RestartExpressionCoroutine(anxietyPhrasesStr, targetSentence, completionNode));
}
else
{
// 如果找不到 MonoBehaviour,直接启动
ExpressionManager.OpenView();
StartExpression(anxietyPhrasesStr, targetSentence, completionNode);
}
}
private static IEnumerator RestartExpressionCoroutine(string anxietyPhrasesStr, string targetSentence, string completionNode)
{
yield return null; // 等一帧
ExpressionManager.OpenView();
StartExpression(anxietyPhrasesStr, targetSentence, completionNode);
}
/// <summary>
/// 淡入 ExpressionManager 的 Sprite 图片
/// <<expression_sprite_fade_in>>
@@ -167,6 +129,37 @@ namespace AibisDream.MiniGame.Language
}
yield return ExpressionManager.StartCoroutine(ExpressionManager.FadeOutSpriteOverlay(duration));
}
/// <summary>
/// Glitch 噪波过渡,等待完成后再继续下一步。
/// <<glitch_transition 0.95 2>> -- 从 0 到 0.952 秒
/// <<glitch_transition 0.95>> -- 立即到 0.95duration 默认 0
/// <<glitch_transition 0.3 0.45 1>> -- 从 0.3 到 0.451 秒
/// </summary>
[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));
}
/// <summary>
/// Glitch 噪波过渡(指定起止值),等待完成后再继续下一步。
/// </summary>
[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));
}
}
}
@@ -95,25 +95,6 @@
---
### 5. 重新启动表达系统 `restart_expression`
关闭当前系统并使用新配置重新启动,相当于 `close_expression` + `open_expression` + `start_expression`
**语法:**
```yarn
<<restart_expression "焦虑短句1|焦虑短句2|焦虑短句3" "目标句子" "完成对话节点">>
```
**参数说明:** 与 `open_expression` 相同
**示例:**
```yarn
// 切换到新的配置
<<restart_expression "害怕|恐惧|退缩" "勇往直前" "NextStage">>
```
---
## 完整使用流程示例
```yarn
@@ -175,8 +156,8 @@ title: Stage2
旁白:第二阶段:建立信心
// 重新启动,使用新配置
<<restart_expression "做不到|太难了|放弃" "我能做到" "Stage3">>
// 切换新配置(视图已打开时复用 start_expression
<<start_expression "做不到|太难了|放弃" "我能做到" "Stage3">>
===
@@ -185,8 +166,8 @@ title: Stage3
旁白:最终阶段:突破自我
// 再次重新启动
<<restart_expression "不可能|不行|失败" "成功突破" "GameComplete">>
// 再次切换配置
<<start_expression "不可能|不行|失败" "成功突破" "GameComplete">>
===