Files
aibis-dream/Assets/Scripts/SceneManagement/DreamDoorSystem.cs
T

946 lines
34 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 Sprite whiteDoorFrameSprite;
[SerializeField] private Sprite seaDoorFrameSprite;
[SerializeField] private SpriteRenderer sceneBlackOverlay;
[Header("Door Sea Animation")]
[SerializeField] private Animator glassSeaAnimator;
[SerializeField] private string glassSeaStateName = "DoorGlassSeaLoop";
[SerializeField] private string glassSeaFramedStateName = "DoorGlassSeaFramedLoop";
[SerializeField, Min(1f)] private float glassSeaCoverOverscan = 1.02f;
[Header("Code Sea Animation")]
[Tooltip("代码海切入时的固定波浪相位(单位:字符网格行)。用于匹配金色海浪切换前的构图。")]
[SerializeField] private float codeSeaStartPhase = 7.5f;
[Tooltip("代码海由远向近推进的速度(单位:字符网格行/秒)。")]
[SerializeField, Min(0f)] private float codeSeaWaveSpeed = 2.6f;
[Tooltip("0/1 和波浪符号独立于海浪移动的闪烁速度。")]
[SerializeField, Min(0f)] private float codeSeaGlyphSpeed = 3.2f;
[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;
[Tooltip("变形为终端开始时并行播放的 TimelineTimelineCenter 名称,如 姐姐/头痛;留空则不播)")]
[SerializeField] private string morphStartTimelineName = "姐姐/Glitch";
private Transform _codeSeaTransform;
private Vector3 _codeSeaOriginalLocalPosition;
private Vector3 _codeSeaOriginalScale;
private Color _codeSeaOriginalColor;
private Transform _terminalUiTransform;
private Vector3 _terminalUiOriginalScale;
private Vector3 _terminalUiOriginalLocalPosition;
private Transform _doorShellTransform;
private Vector3 _doorShellOriginalScale;
private Vector3 _glassSeaOriginalLocalPosition;
private Vector3 _glassSeaOriginalLocalScale;
private string _glassSeaCurrentStateName;
private MaterialPropertyBlock _codeSeaPropertyBlock;
private bool _codeSeaPlaybackActive;
private static readonly int WaveStartTimeId = Shader.PropertyToID("_WaveStartTime");
private static readonly int WavePhaseId = Shader.PropertyToID("_WavePhase");
private static readonly int WaveSpeedId = Shader.PropertyToID("_WaveSpeed");
private static readonly int GlyphSpeedId = Shader.PropertyToID("_GlyphSpeed");
private DoorPresentationPhase _presentationPhase = DoorPresentationPhase.Hidden;
private enum FrameHidePhase
{
Never,
OnCodeSea,
OnTerminal
}
private enum DoorPresentationPhase
{
Hidden,
Door,
GlassSea,
CodeSea,
Terminal
}
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)
{
_glassSeaOriginalLocalPosition = glassSea.transform.localPosition;
_glassSeaOriginalLocalScale = glassSea.transform.localScale;
if (glassSeaAnimator == null)
glassSeaAnimator = glassSea.GetComponent<Animator>();
glassSea.SetAlpha(0);
StopGlassSeaAnimation(resetToFirstFrame: true);
glassSea.gameObject.SetActive(false);
}
SetDoorFrameSprite(hasSea: false);
if (codeSea != null)
{
_codeSeaTransform = codeSea.transform;
_codeSeaOriginalLocalPosition = _codeSeaTransform.localPosition;
_codeSeaOriginalScale = _codeSeaTransform.localScale;
_codeSeaOriginalColor = codeSea.color;
codeSea.SetAlpha(0);
codeSea.gameObject.SetActive(false);
}
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;
}
_presentationPhase = DoorPresentationPhase.Hidden;
}
/// <summary>
/// 显示普通门。该命令是一个完整状态切换,会清掉之前残留的海景、代码海浪和终端。
/// </summary>
public IEnumerator ShowDoor(float duration = 0f)
{
StopAllEffects();
EnsureDoorBaseVisible(hasSea: false);
ResetGlassSeaImmediate();
ResetCodeSeaImmediate();
ResetTerminalImmediate();
_presentationPhase = DoorPresentationPhase.Door;
if (duration > 0f)
yield return new WaitForSeconds(duration);
}
/// <summary>
/// 进入“仔细听”的门近景。首次是白底门,之后直接显示门后的金色海浪。
/// </summary>
public IEnumerator ShowListenView(bool showSea, float duration = 0f)
{
StopAllEffects();
EnsureDoorBaseVisible(showSea);
ResetCodeSeaImmediate();
ResetTerminalImmediate();
Day2SleepPresentationController.Instance?.SetLargePresentationImmediate(1f, 1f);
if (showSea && glassSea != null)
{
glassSea.SetAlpha(1f);
StartGlassSeaAnimation(glassSeaFramedStateName);
_presentationPhase = DoorPresentationPhase.GlassSea;
}
else
{
ResetGlassSeaImmediate();
_presentationPhase = DoorPresentationPhase.Door;
}
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)
{
if (glassSea == null)
yield break;
StopAllEffects();
EnsureDoorBaseVisible(hasSea: true);
ResetCodeSeaImmediate();
ResetTerminalImmediate();
if (_presentationPhase != DoorPresentationPhase.GlassSea || !glassSea.gameObject.activeSelf)
glassSea.SetAlpha(0f);
StartGlassSeaAnimation(glassSeaFramedStateName);
_presentationPhase = DoorPresentationPhase.GlassSea;
yield return glassSea.DOFade(1f, Mathf.Max(0f, duration))
.SetEase(Ease.InOutSine)
.WaitForCompletion();
}
public IEnumerator HideGlassSea(float duration = 1f)
{
if (glassSea == null)
{
yield break;
}
DOTween.Kill(glassSea);
if (glassSea.gameObject.activeSelf)
{
yield return glassSea.DOFade(0f, Mathf.Max(0f, duration))
.SetEase(Ease.InOutSine)
.WaitForCompletion();
}
glassSea.SetAlpha(0f);
StopGlassSeaAnimation(resetToFirstFrame: true);
glassSea.gameObject.SetActive(false);
if (_presentationPhase == DoorPresentationPhase.GlassSea)
_presentationPhase = DoorPresentationPhase.Door;
}
/// <summary>
/// 只调整近景门图片的缩放与透明度,不包含白幕、Bloom 或海浪状态切换。
/// </summary>
public IEnumerator TweenListenCloseup(float scale, float alpha, float duration)
{
var presentation = Day2SleepPresentationController.Instance;
if (presentation == null)
yield break;
yield return presentation.AnimateLargePresentation(scale, alpha, duration);
}
public void SetListenCloseup(float scale, float alpha)
{
Day2SleepPresentationController.Instance?.SetLargePresentationImmediate(scale, alpha);
}
/// <summary>
/// 全白遮罩下调用的瞬时状态切换:隐藏门底和门框,仅显示全屏金色海浪。
/// </summary>
public void ShowGlassSeaFullscreen()
{
if (doorRoot != null)
doorRoot.gameObject.SetActive(true);
SetBackgroundVisible(false);
SetFrameVisible(false);
ResetCodeSeaImmediate();
EnsureGlassSeaVisible(glassSeaStateName);
}
/// <summary>
/// 全白遮罩下调用的瞬时状态切换:恢复门底和门框,金色海浪留在门后。
/// </summary>
public void ShowGlassSeaFramed()
{
ResetCodeSeaImmediate();
EnsureDoorBaseVisible(hasSea: true);
EnsureGlassSeaVisible(glassSeaFramedStateName);
}
/// <summary>
/// 全白时切到无门的全屏 CodeSea;状态职责与 ShowGlassSeaFullscreen 一致。
/// </summary>
public void ShowCodeSeaFullscreen()
{
if (doorRoot != null)
doorRoot.gameObject.SetActive(true);
var restartPlayback = _presentationPhase != DoorPresentationPhase.CodeSea
|| !_codeSeaPlaybackActive;
SetBackgroundVisible(false);
SetFrameVisible(false);
ResetGlassSeaImmediate();
EnsureCodeSeaVisible(restartPlayback);
}
/// <summary>
/// 全白时恢复门框,CodeSea 保持显示在门内。
/// </summary>
public void ShowCodeSeaFramed()
{
var restartPlayback = _presentationPhase != DoorPresentationPhase.CodeSea
|| !_codeSeaPlaybackActive;
ResetGlassSeaImmediate();
EnsureDoorBaseVisible(hasSea: false);
EnsureCodeSeaFramedVisible(restartPlayback);
}
private void ResetListenTransitionImmediate()
{
var panel = UIManager.Instance != null
? UIManager.Instance.GetPanel<ScreenTransitionPanel>()
: null;
panel?.FadeOutSync(0f);
Day2SleepPresentationController.Instance?.ResetLargePresentationImmediate();
}
#endregion
#region Phase 2: CodeSea 浮现 (GlassSea 交叉淡出)
public IEnumerator ShowCodeSea(float duration = 2f)
{
if (codeSea == null)
yield break;
var restartPlayback = _presentationPhase != DoorPresentationPhase.CodeSea
|| !_codeSeaPlaybackActive;
StopAllEffects();
EnsureDoorBaseVisible(hasSea: false);
ResetTerminalImmediate();
codeSea.color = _codeSeaOriginalColor;
if (_codeSeaTransform != null)
{
_codeSeaTransform.localPosition = _codeSeaOriginalLocalPosition;
_codeSeaTransform.localScale = _codeSeaOriginalScale;
}
codeSea.SetAlpha(0f);
codeSea.gameObject.SetActive(true);
BeginCodeSeaPlayback(restartPlayback);
_presentationPhase = DoorPresentationPhase.CodeSea;
if (frameHidePhase == FrameHidePhase.OnCodeSea)
SetFrameVisible(false);
var seq = DOTween.Sequence();
seq.Append(codeSea.DOFade(1f, Mathf.Max(0f, duration)).SetEase(Ease.InOutSine));
if (glassSea != null && glassSea.gameObject.activeSelf)
{
seq.Join(glassSea.DOFade(0f, Mathf.Max(0f, duration) * 0.7f).SetEase(Ease.InSine));
}
yield return seq.WaitForCompletion();
if (glassSea != null)
{
glassSea.SetAlpha(0f);
StopGlassSeaAnimation(resetToFirstFrame: true);
glassSea.gameObject.SetActive(false);
}
}
#endregion
#region Phase 3: CodeSea 变形为终端
public IEnumerator ShowTerminal()
{
if (doorRoot != null)
doorRoot.gameObject.SetActive(true);
if (terminalUI != null)
terminalUI.gameObject.SetActive(true);
_presentationPhase = DoorPresentationPhase.Terminal;
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)
{
StopGlassSeaAnimation(resetToFirstFrame: true);
glassSea.gameObject.SetActive(false);
}
if (codeSea != null)
{
codeSea.gameObject.SetActive(false);
ResetCodeSeaPlayback();
}
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 (!string.IsNullOrEmpty(morphStartTimelineName))
TimelineCenter.Instance.PlayTimeline(morphStartTimelineName);
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(glassSea);
DOTween.Kill(_codeSeaTransform);
DOTween.Kill(codeSea);
DOTween.Kill(_terminalUiTransform);
DOTween.Kill(_doorShellTransform);
DOTween.Kill(flashOverlay);
DOTween.Kill(sceneBlackOverlay);
Day2SleepPresentationController.Instance?.StopLargePresentationAnimation();
}
#endregion
#region 清理
public void HideAll()
{
StopAllEffects();
ResetListenTransitionImmediate();
HideDoor();
SetBackgroundVisible(true);
SetFrameVisible(true);
SetDoorFrameSprite(hasSea: false);
ResetDoorShellScale();
if (glassSea != null)
{
glassSea.SetAlpha(0);
StopGlassSeaAnimation(resetToFirstFrame: true);
glassSea.gameObject.SetActive(false);
}
if (codeSea != null)
{
codeSea.color = _codeSeaOriginalColor;
codeSea.SetAlpha(0);
codeSea.gameObject.SetActive(false);
if (_codeSeaTransform != null)
_codeSeaTransform.localScale = _codeSeaOriginalScale;
}
ResetCodeSeaPlayback();
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);
Day2SleepPresentationController.Instance?.HideLargePresentationImmediate();
_presentationPhase = DoorPresentationPhase.Hidden;
}
#endregion
#region Presentation State
private void EnsureDoorBaseVisible(bool hasSea = false)
{
if (doorRoot != null)
doorRoot.gameObject.SetActive(true);
SetBackgroundVisible(true);
SetFrameVisible(true);
SetDoorFrameSprite(hasSea);
ResetDoorShellScale();
}
private void ResetGlassSeaImmediate()
{
DOTween.Kill(glassSea);
if (glassSea != null)
{
glassSea.SetAlpha(0f);
StopGlassSeaAnimation(resetToFirstFrame: true);
glassSea.gameObject.SetActive(false);
}
}
private void EnsureGlassSeaVisible(string stateName)
{
if (glassSea == null)
return;
glassSea.SetAlpha(1f);
if (!glassSea.gameObject.activeSelf
|| glassSeaAnimator == null
|| !glassSeaAnimator.enabled
|| _glassSeaCurrentStateName != stateName)
StartGlassSeaAnimation(stateName);
else
FitGlassSeaToCameraCover();
_presentationPhase = DoorPresentationPhase.GlassSea;
}
private void ResetCodeSeaImmediate()
{
DOTween.Kill(_codeSeaTransform);
DOTween.Kill(codeSea);
ResetCodeSeaPlayback();
if (codeSea == null)
return;
codeSea.color = _codeSeaOriginalColor;
codeSea.SetAlpha(0f);
codeSea.gameObject.SetActive(false);
if (_codeSeaTransform != null)
{
_codeSeaTransform.localPosition = _codeSeaOriginalLocalPosition;
_codeSeaTransform.localScale = _codeSeaOriginalScale;
}
}
private void EnsureCodeSeaVisible(bool restartPlayback)
{
if (codeSea == null)
return;
codeSea.color = _codeSeaOriginalColor;
codeSea.SetAlpha(1f);
codeSea.gameObject.SetActive(true);
BeginCodeSeaPlayback(restartPlayback);
FitCodeSeaToCameraCover();
_presentationPhase = DoorPresentationPhase.CodeSea;
}
private void EnsureCodeSeaFramedVisible(bool restartPlayback)
{
if (codeSea == null)
return;
codeSea.color = _codeSeaOriginalColor;
codeSea.SetAlpha(1f);
codeSea.gameObject.SetActive(true);
BeginCodeSeaPlayback(restartPlayback);
if (_codeSeaTransform != null)
{
_codeSeaTransform.localPosition = _codeSeaOriginalLocalPosition;
_codeSeaTransform.localScale = _codeSeaOriginalScale;
}
_presentationPhase = DoorPresentationPhase.CodeSea;
}
private void BeginCodeSeaPlayback(bool restartPlayback)
{
if (codeSea == null || (_codeSeaPlaybackActive && !restartPlayback))
return;
_codeSeaPropertyBlock ??= new MaterialPropertyBlock();
codeSea.GetPropertyBlock(_codeSeaPropertyBlock);
_codeSeaPropertyBlock.SetFloat(WaveStartTimeId, Time.time);
_codeSeaPropertyBlock.SetFloat(WavePhaseId, codeSeaStartPhase);
_codeSeaPropertyBlock.SetFloat(WaveSpeedId, codeSeaWaveSpeed);
_codeSeaPropertyBlock.SetFloat(GlyphSpeedId, codeSeaGlyphSpeed);
codeSea.SetPropertyBlock(_codeSeaPropertyBlock);
_codeSeaPlaybackActive = true;
}
private void ResetCodeSeaPlayback()
{
_codeSeaPlaybackActive = false;
}
private void ResetTerminalImmediate()
{
DOTween.Kill(_terminalUiTransform);
if (terminalUI != null)
{
terminalUI.Alpha = 0f;
terminalUI.ResetVisualState();
}
if (_terminalUiTransform != null)
{
_terminalUiTransform.localPosition = _terminalUiOriginalLocalPosition;
_terminalUiTransform.localScale = _terminalUiOriginalScale;
}
if (flashOverlay != null)
flashOverlay.SetAlpha(0f);
if (sceneBlackOverlay != null)
sceneBlackOverlay.SetAlpha(0f);
}
#endregion
#region GlassSea Animation
private void StartGlassSeaAnimation(string stateName = null)
{
if (glassSea == null) return;
stateName = string.IsNullOrEmpty(stateName) ? glassSeaStateName : stateName;
glassSea.gameObject.SetActive(true);
if (glassSeaAnimator != null && glassSeaAnimator.runtimeAnimatorController != null)
{
glassSeaAnimator.enabled = true;
glassSeaAnimator.Play(stateName, 0, 0f);
glassSeaAnimator.Update(0f);
_glassSeaCurrentStateName = stateName;
}
FitGlassSeaToCameraCover();
}
private void StopGlassSeaAnimation(bool resetToFirstFrame)
{
if (glassSeaAnimator == null) return;
if (resetToFirstFrame
&& glassSeaAnimator.runtimeAnimatorController != null
&& glassSeaAnimator.gameObject.activeInHierarchy)
{
var stateName = string.IsNullOrEmpty(_glassSeaCurrentStateName)
? glassSeaStateName
: _glassSeaCurrentStateName;
glassSeaAnimator.enabled = true;
glassSeaAnimator.Play(stateName, 0, 0f);
glassSeaAnimator.Update(0f);
}
glassSeaAnimator.enabled = false;
_glassSeaCurrentStateName = null;
}
private void FitGlassSeaToCameraCover()
{
FitSpriteToCameraCover(
glassSea,
_glassSeaOriginalLocalPosition,
_glassSeaOriginalLocalScale);
}
private void FitCodeSeaToCameraCover()
{
FitSpriteToCameraCover(
codeSea,
_codeSeaOriginalLocalPosition,
_codeSeaOriginalScale);
}
private void FitSpriteToCameraCover(
SpriteRenderer target,
Vector3 originalLocalPosition,
Vector3 originalLocalScale)
{
if (target == null || target.sprite == null) return;
var targetCamera = ResolveDoorCamera();
if (targetCamera == null) return;
var seaTransform = target.transform;
var parent = seaTransform.parent;
var depth = Vector3.Dot(seaTransform.position - targetCamera.transform.position, targetCamera.transform.forward);
depth = Mathf.Max(depth, targetCamera.nearClipPlane + 0.05f);
float viewWidth;
float viewHeight;
if (targetCamera.orthographic)
{
viewHeight = targetCamera.orthographicSize * 2f;
viewWidth = viewHeight * targetCamera.aspect;
}
else
{
var bottomLeft = targetCamera.ViewportToWorldPoint(new Vector3(0f, 0f, depth));
var bottomRight = targetCamera.ViewportToWorldPoint(new Vector3(1f, 0f, depth));
var topLeft = targetCamera.ViewportToWorldPoint(new Vector3(0f, 1f, depth));
viewWidth = Vector3.Distance(bottomLeft, bottomRight);
viewHeight = Vector3.Distance(bottomLeft, topLeft);
}
var spriteSize = target.sprite.bounds.size;
if (spriteSize.x <= 0.001f || spriteSize.y <= 0.001f) return;
var parentScale = parent != null ? parent.lossyScale : Vector3.one;
var localScale = Mathf.Max(
viewWidth / (spriteSize.x * Mathf.Max(Mathf.Abs(parentScale.x), 0.001f)),
viewHeight / (spriteSize.y * Mathf.Max(Mathf.Abs(parentScale.y), 0.001f)));
localScale *= glassSeaCoverOverscan;
var worldCenter = targetCamera.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, depth));
var localCenter = parent != null ? parent.InverseTransformPoint(worldCenter) : worldCenter;
localCenter.z = originalLocalPosition.z;
seaTransform.localPosition = localCenter;
seaTransform.localScale = new Vector3(localScale, localScale, originalLocalScale.z);
}
private Camera ResolveDoorCamera()
{
foreach (var candidate in Camera.allCameras)
{
if (candidate != null
&& candidate.isActiveAndEnabled
&& candidate.gameObject.scene == gameObject.scene)
return candidate;
}
return Camera.main;
}
#endregion
private void SetFrameVisible(bool visible)
{
if (frameRenderers == null) return;
foreach (var frameRenderer in frameRenderers)
{
if (frameRenderer != null)
frameRenderer.enabled = visible;
}
}
private void SetDoorFrameSprite(bool hasSea)
{
var targetSprite = hasSea ? seaDoorFrameSprite : whiteDoorFrameSprite;
if (targetSprite == null)
return;
if (frameRenderers != null)
{
foreach (var frameRenderer in frameRenderers)
{
if (frameRenderer is SpriteRenderer spriteRenderer)
spriteRenderer.sprite = targetSprite;
}
}
Day2SleepPresentationController.Instance?.SetLargeSpriteImmediate(targetSprite);
}
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
}
}