using System.Collections; using AibisDream.Kit; using AibisDream.UI; using AibisDream.Utility; using DG.Tweening; using UnityEngine; namespace AibisDream { public class DreamDoorSystem : Singleton { [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); [Tooltip("终端变形时 sceneBlackOverlay 淡入目标透明度(背景压暗)")] [SerializeField] private float blackoutAlpha = 0.92f; [Tooltip("黑场淡入时长(与终端 morph 并行)")] [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; } } /// /// 显示整个门(仅 background + Frame,GlassSea/CodeSea 保持隐藏直到各自命令调用) /// 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); } /// /// 隐藏整个门 /// 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.gameObject.SetActive(true); sceneBlackOverlay.SetAlpha(0f); } if (backgroundCutDelay > 0f) yield return new WaitForSeconds(backgroundCutDelay); if (preMorphHoldDuration > 0f) yield return new WaitForSeconds(preMorphHoldDuration); if (terminalUI != null) { float morph = 0f; var morphTween = DOTween.To(() => morph, v => { morph = v; terminalUI.MorphProgress = v; }, 1f, 0.82f).SetEase(morphEase); if (sceneBlackOverlay != null) sceneBlackOverlay.DOFade(blackoutAlpha, Mathf.Max(0.01f, blackoutFadeInDuration)); yield return morphTween.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 } }