using System.Collections;
using DG.Tweening;
using UnityEngine;
using AibisDream.FixSystem;
using AibisDream.MiniGame.Language;
using System.Collections.Generic;
namespace AibisDream
{
///
/// 表达系统顶层管理器(原 ExpressionManager,现改为语言粒子系统)
///
public class ExpressionManager : MonoBehaviour
{
[Header("系统组件")]
[SerializeField] private LanguageParticleManager particleManager;
[SerializeField] private GameObject expressionView;
[SerializeField] private LogReleasePresentationController logReleasePresentation;
[SerializeField] private ExpressionScreenPresentationController screenPresentation;
[Header("默认配置")]
[SerializeField] private List defaultAnxietyPhrases = new List
{
"哈哈",
"嘿嘿",
"呵呵"
};
[SerializeField] private string defaultTargetSentence = "这是一个测试示例";
[Header("Sprite 淡入淡出")]
[SerializeField] private SpriteRenderer screenOverlayRenderer;
[SerializeField] private SpriteRenderer volcanoOverlayRenderer;
[SerializeField, Range(0f, 1f)] private float spriteOverlayFadeInAlpha = 1f;
// 屏幕的发光归材质管(M_HuoshanScreenGlitch 的 _Emissive),这里只锁纯白不再叠亮度倍率。
// 曾经有过 screenBloomBrightness 字段,和 _Emissive 叠乘会让屏幕过曝——发光只能有一个真相源。
[Header("Glitch 噪波效果")]
[Tooltip("记忆/对白阶段的透明噪波叠层(glitch_transition)。expression_screen_glitch 已改走屏幕 Shader。")]
[SerializeField] private SpriteNoiseGlitchController glitchController;
[Header("屏幕 Glitch Shader")]
[Tooltip("仅在屏幕淡入 / expression_screen_glitch 时套用;小游戏阶段保持默认 Sprite 材质,避免进 VolFx 盖住粒子")]
[SerializeField] private Material screenGlitchMaterial;
// 观感全部在材质 M_HuoshanScreenGlitch 上调(只有 6 个宏观旋钮:Scale / ColorDamage /
// Warp / Chaos / Grit / SurgeRate)。代码这边只驱动 _GlitchIntensity 档位。
// 不要再把观感参数搬回 Inspector——两处都能改就等于没有真相源,上一版 40 个属性
// 分散在材质和组件两边,结果谁也调不动。
//
// 档位 = 底压 + 脉冲:底压(AddScreenStress)只升不降,是"积蓄的情绪";
// 脉冲(PulseScreenGlitch)瞬间打上去自动衰回底压,是"一句狠话"。
// SetScreenGlitchLevel 保留为绝对档位(会清掉脉冲),兼容旧 Yarn 脚本。
[Tooltip("蓄压底压的软上限;expression_screen_stress 只能加到这里,顶格瞬间留给 pulse 冲出去")]
[SerializeField, Range(0.3f, 1f)] private float screenStressCap = 0.85f;
public LogReleasePresentationController LogReleasePresentation => logReleasePresentation;
/// 当前屏幕 Glitch 档位(0–1),由 expression_screen_glitch 持续保持。
public float ScreenGlitchLevel => screenGlitchIntensity;
private static readonly int ScreenGlitchIntensityId = Shader.PropertyToID("_GlitchIntensity");
private static readonly int ScreenRageId = Shader.PropertyToID("_Rage");
private Material screenDefaultMaterial;
private Material screenFallbackMaterial;
private Material screenGlitchMaterialInstance;
private Coroutine screenGlitchRampRoutine; // 底压过渡 / 泄压
private Coroutine screenGlitchPulseRoutine; // 脉冲衰减
private Coroutine screenRageRoutine; // 荧光转红
private float screenStressFloor; // 蓄压底压(当前值)
private float screenPulse; // 脉冲瞬时值
private float screenGlitchIntensity; // 实际写进材质的合成值
private float screenRage; // 荧光转红程度(0 青绿 / 1 愤怒红)
private bool screenGlitchMaterialActive;
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(true);
}
if (glitchController == null)
{
glitchController = GetComponentInChildren(true);
}
// glitch_transition 用的 noise:必须停在 Default,绝不能进 HuoshanScreen(否则会叠进 VolFx 盖住粒子)。
PrepareNoiseOverlayForGlitchTransition(false);
CacheScreenDefaultMaterial();
// 小游戏阶段绝不能提前套 Glitch 材质:其 SRPDefaultUnlit 会进 VolFx huoshan 合成并盖住粒子。
RestoreScreenDefaultMaterial();
if (logReleasePresentation == null)
{
logReleasePresentation = GetComponent();
}
if (screenPresentation == null)
{
screenPresentation = GetComponent();
}
if (particleManager != null)
{
particleManager.SetPresentationController(logReleasePresentation);
particleManager.SetScreenPresentationController(screenPresentation);
}
}
///
/// 打开表达视图(只显示美术背景,不启动粒子系统)
/// 先设置所有初始状态,再打开视图
///
public void OpenView()
{
screenPresentation?.ResetImmediate();
// 先设置所有初始状态再打开
if (particleManager != null)
{
particleManager.SetInitialStatesBeforeOpen();
}
if (expressionView != null)
{
expressionView.SetActive(true);
}
}
///
/// 启动粒子系统(在打开视图后调用,通常通过 Yarn 对话控制)
///
/// 干扰短句列表(笑话等,非目标粒子从中随机取字符)
/// 目标句子
/// 完成时触发的对话节点名称(可选)
/// 非目标候选粒子总数;<0 时使用 Inspector 默认候选池大小
public void StartSystem(List 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()
{
ResetScreenGlitchState();
// 粒子玩法必须回到默认 Sprite 材质,否则屏幕会进 VolFx 层把字盖掉。
RestoreScreenDefaultMaterial();
List 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);
}
}
///
/// 启动粒子系统(字符串数组版本,方便 Yarn 调用)
///
public void StartSystem(string[] anxietyPhrases, string targetSentence, string completionNodeName = null, int nonTargetParticleTotal = -1)
{
List phrasesList = anxietyPhrases != null ? new List(anxietyPhrases) : null;
StartSystem(phrasesList, targetSentence, completionNodeName, nonTargetParticleTotal);
}
///
/// 若火山释放 log 界面已淡出,则先淡入再返回(start_expression 时调用)
///
/// 淡入时长(秒)
public IEnumerator EnsureReleaseLogFadedIn(float duration = 1f)
{
if (particleManager == null) yield break;
yield return particleManager.FadeInReleaseLogIfNeeded(duration);
}
///
/// 再次 start_expression 前淡出上一轮粒子与连线(若尚未初始化则立即结束)。
/// duration≤0 时使用 LanguageParticleManager 上的默认时长。
///
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();
}
///
/// 等待表达流程就绪(粒子字入场完成即可;panel 动效可并行)
///
public IEnumerator WaitUntilExpressionFlowReady()
{
if (particleManager == null) yield break;
yield return particleManager.WaitUntilExpressionFlowReady();
}
internal IEnumerator PlayScreenDeepAndWait()
{
if (screenPresentation == null)
yield break;
yield return screenPresentation.PlayDeepAndWait();
}
internal bool HasScreenPresentation => screenPresentation != null;
///
/// 从聚焦保持态触发稳定化(概率递增 → 文字锁定 → 排列)
///
public void ResolveFocusSequence()
{
if (particleManager != null)
{
particleManager.ResolveFocusSequence();
}
}
///
/// 触发聚焦稳定化并等待全流程完成
///
public IEnumerator ResolveFocusAndWait()
{
if (particleManager == null) yield break;
particleManager.ResolveFocusSequence();
yield return particleManager.WaitUntilFocusFinished();
}
///
/// 关闭表达系统
///
public void CloseView()
{
screenPresentation?.ResetImmediate();
ResetScreenGlitchState();
ApplyScreenGlitchProperties();
if (logReleasePresentation != null)
{
logReleasePresentation.ForceCleanupImmediate(true);
}
if (expressionView != null)
{
expressionView.SetActive(false);
}
if (particleManager != null)
{
particleManager.StopSystem();
}
}
///
/// 重置系统(保持当前配置)
///
public void ResetSystem(bool playEntranceEffect = true)
{
if (logReleasePresentation != null)
{
logReleasePresentation.ForceCleanupImmediate(true);
}
if (particleManager != null)
{
particleManager.ResetGame(playEntranceEffect);
}
}
///
/// 淡入 Sprite 图片(屏幕、火山同步)
///
/// 淡入时长(秒)
public IEnumerator FadeInSpriteOverlay(float duration = 1f)
{
List 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));
}
// 淡入演出屏幕时才启用 Glitch 材质;强度保持 0,外观与普通屏幕一致。
ActivateScreenGlitchMaterial();
ApplyScreenGlitchProperties();
yield return sequence.WaitForCompletion();
for (int i = 0; i < renderers.Count; i++)
LockScreenOverlayRgb(renderers[i], renderers[i] != null ? renderers[i].color.a : 0f);
}
///
/// 淡出 Sprite 图片(屏幕、火山同步)
///
/// 淡出时长(秒)
public IEnumerator FadeOutSpriteOverlay(float duration = 1f)
{
List renderers = CollectSpriteOverlayRenderers();
if (renderers.Count == 0) yield break;
float fadeDuration = Mathf.Max(0.01f, duration);
// 底压是保持型的:屏幕淡出时一并泄掉,免得下一段淡入时带着上一段积蓄的情绪。
ReleaseScreenStress(fadeDuration);
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 CollectSpriteOverlayRenderers()
{
var result = new List();
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;
locked.a = Mathf.Clamp01(alpha);
sr.color = locked;
}
private void CacheScreenDefaultMaterial()
{
if (screenOverlayRenderer == null || screenDefaultMaterial != null)
return;
var current = screenOverlayRenderer.sharedMaterial;
// 若场景里误挂了 Glitch 材质,仍回退到 URP 默认 Sprite,保证小游戏可见。
if (current != null && current.shader != null &&
current.shader.name == "AibisDream/HuoshanScreenGlitch")
{
screenDefaultMaterial = null;
return;
}
screenDefaultMaterial = current;
}
private void RestoreScreenDefaultMaterial()
{
if (screenOverlayRenderer == null)
return;
CacheScreenDefaultMaterial();
screenGlitchMaterialActive = false;
screenStressFloor = 0f;
screenPulse = 0f;
screenGlitchIntensity = 0f;
screenRage = 0f;
if (screenDefaultMaterial != null)
{
screenOverlayRenderer.sharedMaterial = screenDefaultMaterial;
return;
}
if (screenOverlayRenderer.sharedMaterial != null &&
screenOverlayRenderer.sharedMaterial.shader != null &&
screenOverlayRenderer.sharedMaterial.shader.name == "AibisDream/HuoshanScreenGlitch")
{
// 场景直接挂了 Glitch 材质时要换掉,否则它的 SRPDefaultUnlit 会进 VolFx 合成盖住粒子。
// 绝不能置 null——SpriteRenderer 的 material 为 null 会渲染成品红方块,
// 表现为 switch_fix_system_to Expression 后闪一下粉色。
screenOverlayRenderer.sharedMaterial = GetScreenFallbackMaterial();
}
}
///
/// 场景没有提供干净的屏幕材质时的兜底:标准 Sprite 材质。
///
private Material GetScreenFallbackMaterial()
{
if (screenFallbackMaterial != null)
return screenFallbackMaterial;
Shader spriteShader = Shader.Find("Sprites/Default");
if (spriteShader == null)
{
Debug.LogError("[ExpressionManager] 找不到 Sprites/Default;屏幕材质无法回退。", this);
return null;
}
screenFallbackMaterial = new Material(spriteShader) { name = "Screen Sprite Fallback" };
return screenFallbackMaterial;
}
private void ActivateScreenGlitchMaterial()
{
if (screenOverlayRenderer == null)
return;
CacheScreenDefaultMaterial();
if (screenGlitchMaterial == null)
{
Debug.LogError(
"[ExpressionManager] 未找到屏幕 Glitch 材质;请把 M_HuoshanScreenGlitch 赋给 screenGlitchMaterial。",
this);
return;
}
if (screenGlitchMaterialInstance == null)
{
screenGlitchMaterialInstance = new Material(screenGlitchMaterial)
{
name = screenGlitchMaterial.name + " (Screen Instance)"
};
}
screenOverlayRenderer.sharedMaterial = screenGlitchMaterialInstance;
screenGlitchMaterialActive = true;
ApplyScreenGlitchProperties();
}
private void ApplyScreenGlitchProperties()
{
if (!screenGlitchMaterialActive || screenGlitchMaterialInstance == null)
return;
// 只驱动强度档位与愤怒红;其余观感是材质的事。
screenGlitchMaterialInstance.SetFloat(ScreenGlitchIntensityId, screenGlitchIntensity);
screenGlitchMaterialInstance.SetFloat(ScreenRageId, screenRage);
}
private void SetScreenGlitchIntensity(float value)
{
screenGlitchIntensity = Mathf.Clamp01(value);
if (screenGlitchIntensity > 0f && !screenGlitchMaterialActive)
ActivateScreenGlitchMaterial();
ApplyScreenGlitchProperties();
}
///
/// 恢复 glitch_transition 用的 noise 叠层到原始层级关系:
/// Default 层 + sortingOrder 10 + OpaqueBackground。
/// 不要对齐/改到 HuoshanScreen,否则会进 VolFx 合成叠在粒子上面。
///
private void PrepareNoiseOverlayForGlitchTransition(bool activateForUse)
{
if (glitchController == null)
return;
const int defaultLayer = 0;
glitchController.gameObject.layer = defaultLayer;
var noiseRenderer = glitchController.GetComponent();
if (noiseRenderer != null)
{
noiseRenderer.sortingLayerID = 0;
noiseRenderer.sortingOrder = 10;
if (activateForUse)
noiseRenderer.enabled = true;
}
// 与场景原始配置一致:不透明黑底噪波(强度 0 时几乎全黑底,由上层 HuoshanScreen 内容盖住中心)。
glitchController.SetCompositeMode(SpriteNoiseGlitchController.CompositeMode.OpaqueBackground);
if (activateForUse && !glitchController.gameObject.activeSelf)
glitchController.gameObject.SetActive(true);
}
///
/// Glitch 噪波过渡:驱动 ExpressView/noise(SpriteNoiseGlitch),非屏幕 Shader。
///
public void TransitionGlitch(float to, float duration = 0f)
{
if (glitchController == null) return;
PrepareNoiseOverlayForGlitchTransition(true);
glitchController.TransitionTo(to, duration);
}
///
/// 把「屏幕」Sprite 的故障档位推到 并保持住(非阻塞)。
/// 绝对档位语义:直接设定底压并清掉脉冲,兼容旧 Yarn 脚本。
/// 逐句积蓄情绪请改用 AddScreenStress / PulseScreenGlitch / ReleaseScreenStress。
///
public void SetScreenGlitchLevel(float level, float duration)
{
float target = Mathf.Clamp01(level);
if (!PrepareScreenGlitch(target))
return;
StopScreenGlitchRoutines();
screenPulse = 0f;
float ramp = Mathf.Max(0f, duration);
if (ramp <= 0.02f || !isActiveAndEnabled)
{
screenStressFloor = target;
ApplyCombinedScreenGlitch();
return;
}
screenGlitchRampRoutine = StartCoroutine(RampScreenStressFloor(target, ramp));
}
///
/// 蓄压(非阻塞):往底压罐里加 并一直保持,只升不降。
/// 上限 screenStressCap——顶格情绪不靠底压堆满,留给 PulseScreenGlitch 冲出去。
///
public void AddScreenStress(float amount, float duration = 0.8f)
{
if (amount <= 0f || !PrepareScreenGlitch(1f))
return;
if (screenGlitchRampRoutine != null)
{
StopCoroutine(screenGlitchRampRoutine);
screenGlitchRampRoutine = null;
}
float target = Mathf.Max(screenStressFloor,
Mathf.Min(screenStressCap, screenStressFloor + amount));
float ramp = Mathf.Max(0f, duration);
if (ramp <= 0.02f || !isActiveAndEnabled)
{
screenStressFloor = target;
ApplyCombinedScreenGlitch();
return;
}
screenGlitchRampRoutine = StartCoroutine(RampScreenStressFloor(target, ramp));
}
///
/// 冲击(非阻塞):瞬间顶到 底压+(合成值上限 1),
/// 随后在 内自动落回底压。
/// 连续冲击取 max,只会顶高不会互相打断。开场底压很低时也能打出响的。
///
public void PulseScreenGlitch(float strength, float decayDuration = 1.6f)
{
if (strength <= 0f || !PrepareScreenGlitch(1f))
return;
screenPulse = Mathf.Max(screenPulse, Mathf.Clamp01(strength));
ApplyCombinedScreenGlitch();
if (screenGlitchPulseRoutine != null)
{
StopCoroutine(screenGlitchPulseRoutine);
screenGlitchPulseRoutine = null;
}
if (!isActiveAndEnabled)
{
screenPulse = 0f;
ApplyCombinedScreenGlitch();
return;
}
screenGlitchPulseRoutine = StartCoroutine(
DecayScreenPulse(Mathf.Max(0.05f, decayDuration)));
}
///
/// 泄压(非阻塞):底压和脉冲一起在 内放掉归零。
///
public void ReleaseScreenStress(float duration = 1.2f)
{
if (screenOverlayRenderer == null)
return;
if (!screenGlitchMaterialActive)
{
ResetScreenGlitchState();
return;
}
StopScreenGlitchRoutines();
float ramp = Mathf.Max(0f, duration);
if (ramp <= 0.02f || !isActiveAndEnabled)
{
screenStressFloor = 0f;
screenPulse = 0f;
ApplyCombinedScreenGlitch();
return;
}
screenGlitchRampRoutine = StartCoroutine(ReleaseScreenStressRoutine(ramp));
}
///
/// 激活材质并确保屏幕可见;目标为 0 且材质未激活时直接清零返回 false。
///
private bool PrepareScreenGlitch(float targetLevel)
{
if (screenOverlayRenderer == null)
return false;
if (targetLevel > 0f)
{
ActivateScreenGlitchMaterial();
if (!screenGlitchMaterialActive)
return false;
// 确保屏幕可见(否则材质在动但物体是关的)。
screenOverlayRenderer.enabled = true;
screenOverlayRenderer.gameObject.SetActive(true);
return true;
}
if (!screenGlitchMaterialActive)
{
screenStressFloor = 0f;
screenPulse = 0f;
screenGlitchIntensity = 0f;
screenRage = 0f;
return false;
}
return true;
}
///
/// 攻击词的屏幕 punch:仅在红屏武装(screenRage > 0)时生效,
/// 力度跟着这个词的 impact 走,衰减很短所以读作"闪一下"而不是"又坏了一档"。
/// 由 expression_truth_attack 自动调用——不要在 Yarn 里逐条手写 pulse,
/// 攻击列表会重调,两边手写必然脱节。
///
public void PunchScreenForAttack(float impact)
{
if (screenRage <= 0.01f || !screenGlitchMaterialActive)
return;
PulseScreenGlitch(Mathf.Clamp(0.18f + impact * 0.34f, 0f, 0.6f) * screenRage, 0.28f);
}
///
/// 把屏幕荧光从青绿推向愤怒红(非阻塞)。LOG3 攻击段用; 0 收回。
/// 只换颜色,不影响故障强度——强度仍归 stress/pulse 管。
/// 红屏武装期间,expression_truth_attack 会自动 punch 屏幕。
///
public void SetScreenRage(float level, float duration = 0.5f)
{
if (screenOverlayRenderer == null)
return;
float target = Mathf.Clamp01(level);
if (target > 0f && !PrepareScreenGlitch(1f))
return;
if (screenRageRoutine != null)
{
StopCoroutine(screenRageRoutine);
screenRageRoutine = null;
}
float ramp = Mathf.Max(0f, duration);
if (ramp <= 0.02f || !isActiveAndEnabled)
{
screenRage = target;
ApplyScreenGlitchProperties();
return;
}
screenRageRoutine = StartCoroutine(RampScreenRage(target, ramp));
}
private IEnumerator RampScreenRage(float target, float duration)
{
float start = screenRage;
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = Mathf.SmoothStep(0f, 1f, elapsed / duration);
screenRage = Mathf.Lerp(start, target, t);
ApplyScreenGlitchProperties();
yield return null;
}
screenRage = target;
ApplyScreenGlitchProperties();
screenRageRoutine = null;
}
private void StopScreenGlitchRoutines()
{
if (screenGlitchRampRoutine != null)
{
StopCoroutine(screenGlitchRampRoutine);
screenGlitchRampRoutine = null;
}
if (screenGlitchPulseRoutine != null)
{
StopCoroutine(screenGlitchPulseRoutine);
screenGlitchPulseRoutine = null;
}
if (screenRageRoutine != null)
{
StopCoroutine(screenRageRoutine);
screenRageRoutine = null;
}
}
private void ResetScreenGlitchState()
{
StopScreenGlitchRoutines();
screenStressFloor = 0f;
screenPulse = 0f;
screenGlitchIntensity = 0f;
screenRage = 0f;
}
private void ApplyCombinedScreenGlitch()
{
SetScreenGlitchIntensity(screenStressFloor + screenPulse);
}
private IEnumerator RampScreenStressFloor(float target, float duration)
{
float start = screenStressFloor;
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = Mathf.SmoothStep(0f, 1f, elapsed / duration);
screenStressFloor = Mathf.Lerp(start, target, t);
ApplyCombinedScreenGlitch();
yield return null;
}
screenStressFloor = target;
ApplyCombinedScreenGlitch();
screenGlitchRampRoutine = null;
}
private IEnumerator DecayScreenPulse(float duration)
{
float start = screenPulse;
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = Mathf.SmoothStep(0f, 1f, elapsed / duration);
screenPulse = Mathf.Lerp(start, 0f, t);
ApplyCombinedScreenGlitch();
yield return null;
}
screenPulse = 0f;
ApplyCombinedScreenGlitch();
screenGlitchPulseRoutine = null;
}
private IEnumerator ReleaseScreenStressRoutine(float duration)
{
float startFloor = screenStressFloor;
float startPulse = screenPulse;
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = Mathf.SmoothStep(0f, 1f, elapsed / duration);
screenStressFloor = Mathf.Lerp(startFloor, 0f, t);
screenPulse = Mathf.Lerp(startPulse, 0f, t);
ApplyCombinedScreenGlitch();
yield return null;
}
screenStressFloor = 0f;
screenPulse = 0f;
ApplyCombinedScreenGlitch();
screenGlitchRampRoutine = null;
}
///
/// Glitch 噪波过渡并等待完成:从当前强度平滑过渡到 to。
///
public IEnumerator TransitionGlitchAndWait(float to, float duration = 0f)
{
if (glitchController == null) yield break;
PrepareNoiseOverlayForGlitchTransition(true);
yield return glitchController.StartCoroutine(glitchController.TransitionToAndWait(to, duration));
}
///
/// Glitch 噪波过渡并等待完成:从当前强度平滑过渡到 to(from 忽略,保持丝滑连续)。
///
public IEnumerator TransitionGlitchAndWait(float from, float to, float duration)
{
if (glitchController == null) yield break;
PrepareNoiseOverlayForGlitchTransition(true);
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()
{
ResetScreenGlitchState();
ApplyScreenGlitchProperties();
if (logReleasePresentation != null)
logReleasePresentation.ForceCleanupImmediate(true);
}
private void OnDestroy()
{
if (screenGlitchMaterialInstance != null)
{
if (Application.isPlaying)
Destroy(screenGlitchMaterialInstance);
else
DestroyImmediate(screenGlitchMaterialInstance);
screenGlitchMaterialInstance = null;
}
if (screenFallbackMaterial != null)
{
if (Application.isPlaying)
Destroy(screenFallbackMaterial);
else
DestroyImmediate(screenFallbackMaterial);
screenFallbackMaterial = null;
}
}
}
}