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

334 lines
13 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;
[Header("默认配置")]
[SerializeField] private List<string> defaultAnxietyPhrases = new List<string>
{
"哈哈",
"嘿嘿",
"呵呵"
};
[SerializeField] private string defaultTargetSentence = "这是一个测试示例";
[Header("Sprite 淡入淡出")]
[SerializeField] private SpriteRenderer spriteRenderer;
[Header("Glitch 噪波效果")]
[Tooltip("噪波故障效果控制器,可选;置于 Expression View 子物体上")]
[SerializeField] private SpriteNoiseGlitchController glitchController;
[Header("常驻细密噪点(不随 glitch 强度消失,营造老屏幕底噪)")]
[Tooltip("常驻噪点密度,0 = 关闭(恢复旧行为:LOG1 无噪点)")]
[SerializeField, Range(0f, 0.3f)] private float ambientNoiseDensity = 0.06f;
[Tooltip("噪点细密程度,越大颗粒越细(glitch 主噪点默认 80")]
[SerializeField, Range(80f, 800f)] private float ambientNoiseScale = 360f;
[Tooltip("常驻噪点亮度(低于 glitch 主噪点,保持底噪感)")]
[SerializeField, Range(0f, 1f)] private float ambientNoiseBrightness = 0.5f;
// 游玩期干扰联动的 glitch 基线;yarn glitch_transition 调用后暂停联动直到下一轮
private float gameplayGlitchBaseline;
private bool yarnGlitchOverride;
private Coroutine glitchPulseRoutine;
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);
}
// 释放log屏幕常驻细密噪点(shader 默认关闭,不影响其他使用该 shader 的场景)
glitchController?.SetAmbientNoise(ambientNoiseDensity, ambientNoiseScale, ambientNoiseBrightness);
}
private void LateUpdate()
{
if (particleManager == null || glitchController == null)
return;
if (particleManager.TryGetFocusedTextBounds(out Bounds textBounds))
{
glitchController.SetCalmField(textBounds);
}
else
{
glitchController.ClearCalmField();
}
}
/// <summary>
/// 打开表达视图(只显示美术背景,不启动粒子系统)
/// 先设置所有初始状态,再打开视图
/// </summary>
public void OpenView()
{
glitchController?.ClearCalmField(true);
// 先设置所有初始状态再打开
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>
/// <param name="interferencePresetIndex">干扰表现档位索引;&lt;0 时使用粒子管理器的全局干扰参数</param>
public void StartSystem(List<string> anxietyPhrases, string targetSentence, string completionNodeName = null, int nonTargetParticleTotal = -1, int interferencePresetIndex = -1)
{
glitchController?.ClearCalmField(true);
// 新一轮重置干扰联动(解除 yarn 覆盖、基线归零)
yarnGlitchOverride = false;
gameplayGlitchBaseline = 0f;
if (particleManager != null)
{
// 使用传入的配置,如果没有则使用默认配置
var phrases = anxietyPhrases ?? defaultAnxietyPhrases;
var sentence = targetSentence ?? defaultTargetSentence;
particleManager.InitializeSystem(phrases, sentence, completionNodeName, nonTargetParticleTotal, interferencePresetIndex);
}
}
/// <summary>
/// 启动粒子系统(字符串数组版本,方便 Yarn 调用)
/// </summary>
public void StartSystem(string[] anxietyPhrases, string targetSentence, string completionNodeName = null, int nonTargetParticleTotal = -1, int interferencePresetIndex = -1)
{
List<string> phrasesList = anxietyPhrases != null ? new List<string>(anxietyPhrases) : null;
StartSystem(phrasesList, targetSentence, completionNodeName, nonTargetParticleTotal, interferencePresetIndex);
}
/// <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)
{
glitchController?.ClearCalmField(true);
if (particleManager == null) yield break;
yield return StartCoroutine(particleManager.FadeOutBeforeNewExpressionRound(duration));
}
/// <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();
}
/// <summary>
/// 关闭表达系统
/// </summary>
public void CloseView()
{
glitchController?.ClearCalmField(true);
if (expressionView != null)
{
expressionView.SetActive(false);
}
if (particleManager != null)
{
particleManager.StopSystem();
}
}
/// <summary>
/// 重置系统(保持当前配置)
/// </summary>
public void ResetSystem(bool playEntranceEffect = true)
{
glitchController?.ClearCalmField(true);
if (particleManager != null)
{
particleManager.ResetGame(playEntranceEffect);
}
}
/// <summary>
/// 淡入 Sprite 图片
/// </summary>
/// <param name="duration">淡入时长(秒)</param>
public IEnumerator FadeInSpriteOverlay(float duration = 1f)
{
if (spriteRenderer == null) yield break;
spriteRenderer.color = new Color(1f, 1f, 1f, 0f);
spriteRenderer.enabled = true;
spriteRenderer.gameObject.SetActive(true);
yield return spriteRenderer.DOFade(0.8f, duration).WaitForCompletion();
}
/// <summary>
/// 淡出 Sprite 图片
/// </summary>
/// <param name="duration">淡出时长(秒)</param>
public IEnumerator FadeOutSpriteOverlay(float duration = 1f)
{
if (spriteRenderer == null) yield break;
yield return spriteRenderer.DOFade(0f, duration).WaitForCompletion();
spriteRenderer.enabled = false;
spriteRenderer.gameObject.SetActive(false);
}
/// <summary>
/// 游玩期干扰联动:平滑过渡到基线强度。yarn 手动驱动过 glitch 后(override)忽略,
/// 直到下一次 StartSystem 重置。
/// </summary>
public void SetGameplayGlitchBaseline(float target, float smoothDuration = 0.5f)
{
if (glitchController == null || yarnGlitchOverride) return;
gameplayGlitchBaseline = Mathf.Clamp01(target);
if (glitchPulseRoutine != null) return; // 脉冲进行中,结束后会回落到新基线
glitchController.TransitionTo(gameplayGlitchBaseline, smoothDuration);
}
/// <summary>
/// Glitch 短脉冲:快速升到 peak 再回落到游玩基线(用于完成瞬间等强调时刻)。
/// </summary>
public void PlayGlitchPulse(float peak, float duration = 0.5f)
{
if (glitchController == null || yarnGlitchOverride) return;
if (glitchPulseRoutine != null)
{
StopCoroutine(glitchPulseRoutine);
}
glitchPulseRoutine = StartCoroutine(GlitchPulseRoutine(Mathf.Clamp01(peak), duration));
}
private IEnumerator GlitchPulseRoutine(float peak, float duration)
{
yield return glitchController.StartCoroutine(
glitchController.TransitionToAndWait(peak, duration * 0.35f));
yield return glitchController.StartCoroutine(
glitchController.TransitionToAndWait(gameplayGlitchBaseline, duration * 0.65f));
glitchPulseRoutine = null;
}
/// <summary>
/// Glitch 噪波过渡:从当前强度平滑过渡到 to,不瞬移(类似 DOTween)。
/// yarn 手动驱动后,游玩期干扰联动暂停(避免互相打架)。
/// </summary>
public void TransitionGlitch(float to, float duration = 0f)
{
if (glitchController == null) return;
MarkYarnGlitchOverride();
glitchController.TransitionTo(to, duration);
}
private void MarkYarnGlitchOverride()
{
yarnGlitchOverride = true;
if (glitchPulseRoutine != null)
{
StopCoroutine(glitchPulseRoutine);
glitchPulseRoutine = null;
}
}
/// <summary>
/// Glitch 噪波过渡并等待完成:从当前强度平滑过渡到 to。
/// </summary>
public IEnumerator TransitionGlitchAndWait(float to, float duration = 0f)
{
if (glitchController == null) yield break;
MarkYarnGlitchOverride();
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;
MarkYarnGlitchOverride();
yield return glitchController.StartCoroutine(glitchController.TransitionToAndWait(to, duration));
}
}
}