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

470 lines
17 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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("屏幕强制保持该 RGB(只动 alpha")]
[SerializeField] private Color lockedScreenRgb = Color.black;
[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)
{
if (particleManager != null)
{
particleManager.SetPresentationController(logReleasePresentation);
// 使用传入的配置,如果没有则使用默认配置
var phrases = anxietyPhrases ?? defaultAnxietyPhrases;
var sentence = targetSentence ?? defaultTargetSentence;
particleManager.InitializeSystem(phrases, sentence, completionNodeName, nonTargetParticleTotal);
}
}
/// <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))
{
SuppressScreenMaterialGlow(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 = lockedScreenRgb;
locked.a = Mathf.Clamp01(alpha);
sr.color = locked;
}
private static void SuppressScreenMaterialGlow(SpriteRenderer sr)
{
if (sr == null) return;
Material mat = sr.material;
if (mat == null) return;
if (mat.HasProperty("_Glow"))
mat.SetFloat("_Glow", 0f);
if (mat.HasProperty("_GlowGlobal"))
mat.SetFloat("_GlowGlobal", 1f);
}
/// <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);
}
}
}