422 lines
14 KiB
C#
422 lines
14 KiB
C#
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 + Frame,GlassSea/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
|
||
}
|
||
}
|