Files
aibis-dream/Assets/Scripts/MiniGame/HuoShan/Language/ExpressionManager.cs
T

485 lines
17 KiB
C#

using System.Collections;
using DG.Tweening;
using UnityEngine;
using AibisDream.FixSystem;
using AibisDream.MiniGame.Language;
using System.Collections.Generic;
namespace AibisDream
{
/// <summary>
/// 表达系统顶层管理器(原 ExpressionManager,现改为语言粒子系统)
/// </summary>
public class ExpressionManager : MonoBehaviour
{
[Header("系统组件")]
[SerializeField] private LanguageParticleManager particleManager;
[SerializeField] private GameObject expressionView;
[SerializeField] private LogReleasePresentationController logReleasePresentation;
[Header("默认配置")]
[SerializeField] private List<string> defaultAnxietyPhrases = new List<string>
{
"哈哈",
"嘿嘿",
"呵呵"
};
[SerializeField] private string defaultTargetSentence = "这是一个测试示例";
[Header("Sprite 淡入淡出")]
[SerializeField] private SpriteRenderer screenOverlayRenderer;
[SerializeField] private SpriteRenderer volcanoOverlayRenderer;
[SerializeField, Range(0f, 1f)] private float spriteOverlayFadeInAlpha = 1f;
[Tooltip("屏幕淡入时的亮度倍率。大于 1 的部分由 Bloom 提取,Sprite 原本的绿色保持不变。")]
[SerializeField, Min(1f)] private float screenBloomBrightness = 1.35f;
[Header("Glitch 噪波效果")]
[Tooltip("噪波故障效果控制器,可选;置于 Expression View 子物体上")]
[SerializeField] private SpriteNoiseGlitchController glitchController;
public LogReleasePresentationController LogReleasePresentation => logReleasePresentation;
void Start()
{
// 注册到系统字典
FixSystemCenter.SystemDic.Register(this);
// 查找并初始化表达视图
if (expressionView == null)
{
expressionView = transform.Find("Expression View")?.gameObject;
}
if (expressionView != null)
{
expressionView.SetActive(false);
}
// 查找粒子管理器
if (particleManager == null)
{
particleManager = GetComponentInChildren<LanguageParticleManager>(true);
}
if (glitchController == null)
{
glitchController = GetComponentInChildren<SpriteNoiseGlitchController>(true);
}
if (logReleasePresentation == null)
{
logReleasePresentation = GetComponent<LogReleasePresentationController>();
}
if (particleManager != null)
{
particleManager.SetPresentationController(logReleasePresentation);
}
}
/// <summary>
/// 打开表达视图(只显示美术背景,不启动粒子系统)
/// 先设置所有初始状态,再打开视图
/// </summary>
public void OpenView()
{
// 先设置所有初始状态再打开
if (particleManager != null)
{
particleManager.SetInitialStatesBeforeOpen();
}
if (expressionView != null)
{
expressionView.SetActive(true);
}
}
/// <summary>
/// 启动粒子系统(在打开视图后调用,通常通过 Yarn 对话控制)
/// </summary>
/// <param name="anxietyPhrases">干扰短句列表(笑话等,非目标粒子从中随机取字符)</param>
/// <param name="targetSentence">目标句子</param>
/// <param name="completionNodeName">完成时触发的对话节点名称(可选)</param>
/// <param name="nonTargetParticleTotal">非目标候选粒子总数;&lt;0 时使用 Inspector 默认候选池大小</param>
public void StartSystem(List<string> anxietyPhrases, string targetSentence, string completionNodeName = null, int nonTargetParticleTotal = -1)
{
HideSpriteOverlaysForMinigame();
if (particleManager != null)
{
particleManager.SetPresentationController(logReleasePresentation);
// 使用传入的配置,如果没有则使用默认配置
var phrases = anxietyPhrases ?? defaultAnxietyPhrases;
var sentence = targetSentence ?? defaultTargetSentence;
particleManager.InitializeSystem(phrases, sentence, completionNodeName, nonTargetParticleTotal);
}
}
private void HideSpriteOverlaysForMinigame()
{
List<SpriteRenderer> renderers = CollectSpriteOverlayRenderers();
for (int i = 0; i < renderers.Count; i++)
{
SpriteRenderer sr = renderers[i];
if (sr == null) continue;
DOTween.Kill(sr);
if (IsScreenOverlayRenderer(sr))
{
LockScreenOverlayRgb(sr, 0f);
}
else
{
Color color = sr.color;
color.a = 0f;
sr.color = color;
}
sr.enabled = false;
sr.gameObject.SetActive(false);
}
}
/// <summary>
/// 启动粒子系统(字符串数组版本,方便 Yarn 调用)
/// </summary>
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, nonTargetParticleTotal);
}
/// <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>
/// 再次 <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));
}
public IEnumerator PreparePresentationForNextRound()
{
if (logReleasePresentation == null) yield break;
yield return logReleasePresentation.PrepareForNextRound();
}
/// <summary>
/// 等待表达流程就绪(开场表现 + 火山表达panel 播完)
/// </summary>
public IEnumerator WaitUntilExpressionFlowReady()
{
if (particleManager == null) yield break;
yield return particleManager.WaitUntilExpressionFlowReady();
}
/// <summary>
/// 从聚焦保持态触发稳定化(概率递增 → 文字锁定 → 排列)
/// </summary>
public void ResolveFocusSequence()
{
if (particleManager != null)
{
particleManager.ResolveFocusSequence();
}
}
/// <summary>
/// 触发聚焦稳定化并等待全流程完成
/// </summary>
public IEnumerator ResolveFocusAndWait()
{
if (particleManager == null) yield break;
particleManager.ResolveFocusSequence();
yield return particleManager.WaitUntilFocusFinished();
}
public IEnumerator PlayFocusInterferenceAndWait(
string direction,
float duration,
string characterPool,
string colorHex,
float shakeAmplitude,
float shakeSpeed,
float changeInterval,
float transitionRatio)
{
if (particleManager == null) yield break;
yield return particleManager.PlayFocusInterferenceAndWait(
direction,
duration,
characterPool,
colorHex,
shakeAmplitude,
shakeSpeed,
changeInterval,
transitionRatio);
}
/// <summary>
/// 开发预览:跳过玩法,直接进入 FocusHolding(目标字已就位)。
/// </summary>
public IEnumerator SkipToFocusHoldingPreview()
{
if (particleManager == null)
yield break;
if (!particleManager.IsInitialized)
yield break;
yield return particleManager.WaitUntilExpressionFlowReady();
particleManager.DebugCompleteTargets();
float timeout = 8f;
float elapsed = 0f;
while (elapsed < timeout &&
particleManager.CurrentCompletionPhase != CompletionPhase.FocusHolding &&
particleManager.CurrentCompletionPhase != CompletionPhase.Focusing)
{
elapsed += Time.deltaTime;
yield return null;
}
while (particleManager.CurrentCompletionPhase == CompletionPhase.Focusing)
yield return null;
}
/// <summary>
/// 关闭表达系统
/// </summary>
public void CloseView()
{
if (logReleasePresentation != null)
{
logReleasePresentation.ForceCleanupImmediate(true);
}
if (expressionView != null)
{
expressionView.SetActive(false);
}
if (particleManager != null)
{
particleManager.StopSystem();
}
}
/// <summary>
/// 重置系统(保持当前配置)
/// </summary>
public void ResetSystem(bool playEntranceEffect = true)
{
if (logReleasePresentation != null)
{
logReleasePresentation.ForceCleanupImmediate(true);
}
if (particleManager != null)
{
particleManager.ResetGame(playEntranceEffect);
}
}
/// <summary>
/// 淡入 Sprite 图片(屏幕、火山同步)
/// </summary>
/// <param name="duration">淡入时长(秒)</param>
public IEnumerator FadeInSpriteOverlay(float duration = 1f)
{
List<SpriteRenderer> renderers = CollectSpriteOverlayRenderers();
if (renderers.Count == 0) yield break;
float fadeDuration = Mathf.Max(0.01f, duration);
float targetAlpha = Mathf.Clamp01(spriteOverlayFadeInAlpha);
Sequence sequence = DOTween.Sequence().SetTarget(this);
for (int i = 0; i < renderers.Count; i++)
{
SpriteRenderer sr = renderers[i];
DOTween.Kill(sr);
PrepareOverlayRendererForFade(sr, 0f);
sr.enabled = true;
sr.gameObject.SetActive(true);
sequence.Join(sr.DOFade(targetAlpha, fadeDuration).SetEase(Ease.OutQuad));
}
yield return sequence.WaitForCompletion();
for (int i = 0; i < renderers.Count; i++)
LockScreenOverlayRgb(renderers[i], renderers[i] != null ? renderers[i].color.a : 0f);
}
/// <summary>
/// 淡出 Sprite 图片(屏幕、火山同步)
/// </summary>
/// <param name="duration">淡出时长(秒)</param>
public IEnumerator FadeOutSpriteOverlay(float duration = 1f)
{
List<SpriteRenderer> renderers = CollectSpriteOverlayRenderers();
if (renderers.Count == 0) yield break;
float fadeDuration = Mathf.Max(0.01f, duration);
Sequence sequence = DOTween.Sequence().SetTarget(this);
for (int i = 0; i < renderers.Count; i++)
{
SpriteRenderer sr = renderers[i];
DOTween.Kill(sr);
LockScreenOverlayRgb(sr, sr.color.a);
sr.enabled = true;
sr.gameObject.SetActive(true);
sequence.Join(sr.DOFade(0f, fadeDuration).SetEase(Ease.InQuad));
}
yield return sequence.WaitForCompletion();
for (int i = 0; i < renderers.Count; i++)
{
SpriteRenderer sr = renderers[i];
if (sr == null) continue;
LockScreenOverlayRgb(sr, 0f);
sr.enabled = false;
sr.gameObject.SetActive(false);
}
}
private List<SpriteRenderer> CollectSpriteOverlayRenderers()
{
var result = new List<SpriteRenderer>();
if (screenOverlayRenderer != null)
result.Add(screenOverlayRenderer);
if (volcanoOverlayRenderer != null && volcanoOverlayRenderer != screenOverlayRenderer)
result.Add(volcanoOverlayRenderer);
return result;
}
private bool IsScreenOverlayRenderer(SpriteRenderer sr)
{
return sr != null && sr == screenOverlayRenderer;
}
private void PrepareOverlayRendererForFade(SpriteRenderer sr, float alpha)
{
if (sr == null) return;
if (IsScreenOverlayRenderer(sr))
{
LockScreenOverlayRgb(sr, alpha);
return;
}
Color color = sr.color;
color.a = alpha;
sr.color = color;
}
private void LockScreenOverlayRgb(SpriteRenderer sr, float alpha)
{
if (!IsScreenOverlayRenderer(sr)) return;
Color locked = Color.white * screenBloomBrightness;
locked.a = Mathf.Clamp01(alpha);
sr.color = locked;
}
/// <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));
}
public IEnumerator BeginExpressionMemory(
string memoryKey,
float fadeDuration = -1f,
float pushSpeed = -1f,
float horizontalSpeed = -1f,
bool preserveGlitch = false,
float targetOpacity = 1f)
{
if (logReleasePresentation == null)
{
Debug.LogError("[ExpressionManager] LogReleasePresentationController 未配置;记忆命令安全结束。");
yield break;
}
yield return logReleasePresentation.BeginMemory(
memoryKey,
fadeDuration,
pushSpeed,
horizontalSpeed,
preserveGlitch,
targetOpacity);
}
public IEnumerator BeginExpressionLie(string keyword, string preset)
{
if (logReleasePresentation == null)
{
Debug.LogError("[ExpressionManager] LogReleasePresentationController 未配置;谎话命令安全结束。");
yield break;
}
yield return logReleasePresentation.BeginLie(keyword, preset);
}
public IEnumerator BreakExpressionLie()
{
if (logReleasePresentation == null) yield break;
yield return logReleasePresentation.BreakLie();
}
public IEnumerator RevealExpressionTruth()
{
if (logReleasePresentation == null) yield break;
yield return logReleasePresentation.RevealTruth();
}
public IEnumerator EndExpressionMemory()
{
if (logReleasePresentation == null) yield break;
yield return logReleasePresentation.EndMemory();
}
public void ImpactExpressionTruth()
{
logReleasePresentation?.TruthImpact();
}
private void OnDisable()
{
if (logReleasePresentation != null)
logReleasePresentation.ForceCleanupImmediate(true);
}
}
}