Files
aibis-dream/Assets/Scripts/SceneManagement/DreamDoorSystem.cs
T
2026-03-06 22:38:17 +08:00

422 lines
14 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 AibisDream.Kit;
using AibisDream.UI;
using AibisDream.Utility;
using DG.Tweening;
using UnityEngine;
namespace AibisDream
{
public class DreamDoorSystem : Singleton<DreamDoorSystem>
{
[Header("Door Root")]
[Tooltip("Door 根节点。默认仅 background 和 Frame 显示,整个 Door 初始隐藏。show_door 命令激活后显示。")]
[SerializeField] private Transform doorRoot;
[Header("Door Sprites")]
[SerializeField] private SpriteRenderer glassSea;
[SerializeField] private SpriteRenderer codeSea;
[SerializeField] private SpriteRenderer flashOverlay;
[Tooltip("仅放门的静态 background,不要把 glassSea/codeSea/overlay 拖进来。")]
[SerializeField] private Renderer[] backgroundRenderers;
[SerializeField] private Renderer[] frameRenderers;
[SerializeField] private SpriteRenderer sceneBlackOverlay;
[Header("Terminal(挂在 Door 子节点上即可,无需 Canvas")]
[SerializeField] private DreamTerminalUI terminalUI;
[Header("Morph Settings")]
[Tooltip("负责门壳贴合的节点;不填则退回到 doorRoot。")]
[SerializeField] private Transform doorShellTransform;
[Tooltip("线框先贴合的图片目标。建议拖一个 SpriteRenderer 作为参考形状。")]
[SerializeField] private SpriteRenderer terminalShapeReference;
[SerializeField] private Vector3 seaCollapseScale = new(0.9f, 0.84f, 1f);
[SerializeField] private Vector3 doorSnapScale = new(0.95f, 1.06f, 1f);
[SerializeField] private Ease morphEase = Ease.InOutCubic;
[SerializeField] private FrameHidePhase frameHidePhase = FrameHidePhase.OnTerminal;
[SerializeField] private float doorSnapDuration = 0.2f;
[SerializeField] private float backgroundCutDelay = 0.04f;
[SerializeField] private float preMorphHoldDuration = 0.16f;
[SerializeField] private float postMorphBootDelay = 0.12f;
[SerializeField] private float referenceSnapMorphProgress = 0.08f;
[SerializeField] private Vector2 referenceSnapPadding = new(0.18f, 0.18f);
[SerializeField] private float blackoutAlpha = 0.92f;
[SerializeField] private float blackoutFadeInDuration = 0.08f;
[SerializeField] private float blackoutFadeOutDuration = 0.12f;
private Transform _codeSeaTransform;
private Vector3 _codeSeaOriginalScale;
private Color _codeSeaOriginalColor;
private Transform _terminalUiTransform;
private Vector3 _terminalUiOriginalScale;
private Vector3 _terminalUiOriginalLocalPosition;
private Transform _doorShellTransform;
private Vector3 _doorShellOriginalScale;
private enum FrameHidePhase
{
Never,
OnCodeSea,
OnTerminal
}
public override void OnSingletonInit()
{
if (doorRoot != null)
doorRoot.gameObject.SetActive(false);
_doorShellTransform = doorShellTransform != null ? doorShellTransform : doorRoot;
if (_doorShellTransform != null)
_doorShellOriginalScale = _doorShellTransform.localScale;
if (glassSea != null)
{
glassSea.SetAlpha(0);
glassSea.gameObject.SetActive(false);
}
if (codeSea != null)
{
codeSea.SetAlpha(0);
codeSea.gameObject.SetActive(false);
_codeSeaTransform = codeSea.transform;
_codeSeaOriginalScale = _codeSeaTransform.localScale;
_codeSeaOriginalColor = codeSea.color;
}
if (flashOverlay != null)
{
flashOverlay.SetAlpha(0);
flashOverlay.gameObject.SetActive(true);
}
if (sceneBlackOverlay != null)
{
sceneBlackOverlay.SetAlpha(0);
sceneBlackOverlay.gameObject.SetActive(true);
}
if (terminalUI != null)
{
terminalUI.ResetVisualState();
terminalUI.Alpha = 0f;
_terminalUiTransform = terminalUI.transform;
_terminalUiOriginalScale = _terminalUiTransform.localScale;
_terminalUiOriginalLocalPosition = _terminalUiTransform.localPosition;
}
}
/// <summary>
/// 显示整个门(仅 background + FrameGlassSea/CodeSea 保持隐藏直到各自命令调用)
/// </summary>
public IEnumerator ShowDoor(float duration = 0f)
{
if (doorRoot != null)
doorRoot.gameObject.SetActive(true);
SetBackgroundVisible(true);
SetFrameVisible(true);
ResetDoorShellScale();
if (duration > 0f)
yield return new WaitForSeconds(duration);
}
/// <summary>
/// 隐藏整个门
/// </summary>
public void HideDoor()
{
if (doorRoot != null)
doorRoot.gameObject.SetActive(false);
}
#region Phase 1: GlassSea 浮现
public IEnumerator ShowGlassSea(float duration = 2f)
{
glassSea.gameObject.SetActive(true);
yield return glassSea.DOFade(1f, duration)
.SetEase(Ease.InOutSine)
.WaitForCompletion();
}
public IEnumerator HideGlassSea(float duration = 1f)
{
yield return glassSea.DOFade(0f, duration)
.SetEase(Ease.InOutSine)
.WaitForCompletion();
glassSea.gameObject.SetActive(false);
}
#endregion
#region Phase 2: CodeSea 浮现 (GlassSea 交叉淡出)
public IEnumerator ShowCodeSea(float duration = 2f)
{
codeSea.gameObject.SetActive(true);
if (frameHidePhase == FrameHidePhase.OnCodeSea)
SetFrameVisible(false);
var seq = DOTween.Sequence();
seq.Append(codeSea.DOFade(1f, duration).SetEase(Ease.InOutSine));
if (glassSea != null && glassSea.gameObject.activeSelf)
{
seq.Join(glassSea.DOFade(0f, duration * 0.7f).SetEase(Ease.InSine));
}
yield return seq.WaitForCompletion();
if (glassSea != null)
glassSea.gameObject.SetActive(false);
}
#endregion
#region Phase 3: CodeSea 变形为终端
public IEnumerator ShowTerminal()
{
yield return AnimateDoorToTerminal();
if (terminalUI != null)
{
if (postMorphBootDelay > 0f)
yield return new WaitForSeconds(postMorphBootDelay);
terminalUI.Begin();
}
}
private IEnumerator AnimateDoorToTerminal()
{
StopAllEffects();
if (terminalUI != null)
{
terminalUI.ResetVisualState();
terminalUI.MorphProgress = 0f;
terminalUI.Alpha = 1f;
}
if (_terminalUiTransform != null)
{
_terminalUiTransform.localPosition = _terminalUiOriginalLocalPosition;
_terminalUiTransform.localScale = _terminalUiOriginalScale;
}
SetBackgroundVisible(false);
SetFrameVisible(false);
if (glassSea != null)
glassSea.gameObject.SetActive(false);
if (codeSea != null)
codeSea.gameObject.SetActive(false);
if (flashOverlay != null)
flashOverlay.SetAlpha(0f);
if (sceneBlackOverlay != null)
sceneBlackOverlay.SetAlpha(0f);
if (preMorphHoldDuration > 0f)
yield return new WaitForSeconds(preMorphHoldDuration);
if (terminalUI != null)
{
float morph = 0f;
yield return DOTween.To(() => morph, v =>
{
morph = v;
terminalUI.MorphProgress = v;
}, 1f, 0.82f).SetEase(morphEase).WaitForCompletion();
}
ResetDoorShellScale();
}
#endregion
#region 终端结局动效
public IEnumerator PlayShutdownEffect()
{
var target = _terminalUiTransform != null ? _terminalUiTransform : _codeSeaTransform;
if (target == null) yield break;
var seq = DOTween.Sequence();
if (flashOverlay != null)
seq.Append(flashOverlay.DOFade(0.6f, 0.15f));
// 压扁成白线
float scaleY = _terminalUiTransform != null ? _terminalUiOriginalScale.y * 0.02f : 0.005f;
seq.Join(target.DOScaleY(scaleY, 0.35f).SetEase(Ease.OutQuad));
// 横向收缩消失
seq.Append(target.DOScaleX(0f, 0.3f).SetEase(Ease.InQuad));
if (flashOverlay != null)
seq.Join(flashOverlay.DOFade(0f, 0.5f));
yield return seq.WaitForCompletion();
}
public void PlayCrashEffect()
{
var target = _terminalUiTransform != null ? _terminalUiTransform : _codeSeaTransform;
if (target != null)
target.DOShakePosition(1.2f, 0.08f, 35, fadeOut: false);
}
public void StopAllEffects()
{
DOTween.Kill(_codeSeaTransform);
DOTween.Kill(codeSea);
DOTween.Kill(_terminalUiTransform);
DOTween.Kill(_doorShellTransform);
DOTween.Kill(flashOverlay);
DOTween.Kill(sceneBlackOverlay);
}
#endregion
#region 清理
public void HideAll()
{
StopAllEffects();
HideDoor();
SetBackgroundVisible(true);
SetFrameVisible(true);
ResetDoorShellScale();
if (glassSea != null)
{
glassSea.SetAlpha(0);
glassSea.gameObject.SetActive(false);
}
if (codeSea != null)
{
codeSea.color = _codeSeaOriginalColor;
codeSea.SetAlpha(0);
codeSea.gameObject.SetActive(false);
if (_codeSeaTransform != null)
_codeSeaTransform.localScale = _codeSeaOriginalScale;
}
if (terminalUI != null)
{
terminalUI.Alpha = 0f;
terminalUI.ResetVisualState();
}
if (_terminalUiTransform != null)
{
_terminalUiTransform.localPosition = _terminalUiOriginalLocalPosition;
_terminalUiTransform.localScale = _terminalUiOriginalScale;
}
if (flashOverlay != null)
flashOverlay.SetAlpha(0);
if (sceneBlackOverlay != null)
sceneBlackOverlay.SetAlpha(0);
}
#endregion
private void SetFrameVisible(bool visible)
{
if (frameRenderers == null) return;
foreach (var frameRenderer in frameRenderers)
{
if (frameRenderer != null)
frameRenderer.enabled = visible;
}
}
private void SetBackgroundVisible(bool visible)
{
if (backgroundRenderers == null) return;
foreach (var backgroundRenderer in backgroundRenderers)
{
if (backgroundRenderer != null)
backgroundRenderer.enabled = visible;
}
}
private void ResetDoorShellScale()
{
if (_doorShellTransform != null)
_doorShellTransform.localScale = _doorShellOriginalScale;
}
private bool HasShapeReference()
{
return terminalShapeReference != null && terminalUI != null && _terminalUiTransform != null;
}
private void FitTerminalFrameToReference()
{
if (!HasShapeReference())
return;
var bounds = terminalShapeReference.bounds;
var targetLocalPosition = _terminalUiTransform.parent != null
? _terminalUiTransform.parent.InverseTransformPoint(bounds.center)
: bounds.center;
_terminalUiTransform.localPosition = new Vector3(
targetLocalPosition.x,
targetLocalPosition.y,
_terminalUiOriginalLocalPosition.z
);
Vector2 baseSize = terminalUI.EvaluateFrameSize(referenceSnapMorphProgress);
float targetWidth = Mathf.Max(0.01f, bounds.size.x + referenceSnapPadding.x);
float targetHeight = Mathf.Max(0.01f, bounds.size.y + referenceSnapPadding.y);
Vector3 parentLossyScale = _terminalUiTransform.parent != null ? _terminalUiTransform.parent.lossyScale : Vector3.one;
float parentScaleX = Mathf.Max(0.0001f, Mathf.Abs(parentLossyScale.x));
float parentScaleY = Mathf.Max(0.0001f, Mathf.Abs(parentLossyScale.y));
float localScaleX = targetWidth / Mathf.Max(0.0001f, baseSize.x * parentScaleX);
float localScaleY = targetHeight / Mathf.Max(0.0001f, baseSize.y * parentScaleY);
_terminalUiTransform.localScale = new Vector3(
Mathf.Sign(_terminalUiOriginalScale.x) * localScaleX,
Mathf.Sign(_terminalUiOriginalScale.y) * localScaleY,
_terminalUiOriginalScale.z
);
}
#if UNITY_EDITOR
private void OnDrawGizmosSelected()
{
if (codeSea == null) return;
Gizmos.color = new Color(1f, 0.7f, 0f, 0.4f);
var bounds = codeSea.bounds;
Gizmos.DrawWireCube(bounds.center, bounds.size);
if (terminalShapeReference != null)
{
Gizmos.color = new Color(1f, 1f, 1f, 0.45f);
var referenceBounds = terminalShapeReference.bounds;
Gizmos.DrawWireCube(referenceBounds.center, referenceBounds.size);
}
Gizmos.color = new Color(0f, 1f, 1f, 0.3f);
Vector3 termSize = new(
bounds.size.x * seaCollapseScale.x,
bounds.size.y * seaCollapseScale.y,
bounds.size.z
);
Gizmos.DrawWireCube(bounds.center, termSize);
}
#endif
}
}