338 lines
13 KiB
C#
338 lines
13 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;
|
||
|
||
[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 = 0f;
|
||
[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">非目标候选粒子总数;<0 时使用 Inspector 默认候选池大小</param>
|
||
/// <param name="interferencePresetIndex">干扰表现档位索引;<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;
|
||
glitchController?.TransitionTo(0f, 0.25f);
|
||
|
||
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);
|
||
glitchController?.TransitionTo(0f, 0.25f);
|
||
|
||
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);
|
||
glitchController?.TransitionTo(0f, 0f);
|
||
|
||
if (expressionView != null)
|
||
{
|
||
expressionView.SetActive(false);
|
||
}
|
||
|
||
if (particleManager != null)
|
||
{
|
||
particleManager.StopSystem();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重置系统(保持当前配置)
|
||
/// </summary>
|
||
public void ResetSystem(bool playEntranceEffect = true)
|
||
{
|
||
glitchController?.ClearCalmField(true);
|
||
glitchController?.TransitionTo(0f, 0.25f);
|
||
|
||
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));
|
||
}
|
||
|
||
}
|
||
}
|