fix(dreamdoor): 近景门框改大图并修复 closeup 残留
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,18 +7,29 @@ using UnityEngine;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
/// <summary>
|
||||
/// Day2 门演出。可见门框与门后内容分层如下,勿混用:
|
||||
/// <list type="bullet">
|
||||
/// <item><b>可见门框(近景)</b>:只用 SpriteShowcase 大图(白色门框/金色门框)。
|
||||
/// 缩放/透明度走 set_door_closeup / tween_door_closeup,不要再 show_large_sprite「门框特写」叠一层。</item>
|
||||
/// <item><b>Door/background</b>:HDR 白底或门后内容,从大图透明洞透出。</item>
|
||||
/// <item><b>Door/Frame</b>:场景里保持 inactive,缩放与近景大图不同;仅作 terminalShapeReference / 贴图同步,不要 SetActive 当第二层门框。</item>
|
||||
/// <item><b>远景整门立绘</b>:hide_dream_door + show_large_sprite「D2S门」,不走本系统 Frame。</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public class DreamDoorSystem : Singleton<DreamDoorSystem>
|
||||
{
|
||||
[Header("Door Root")]
|
||||
[Tooltip("Door 根节点。默认仅 background 和 Frame 显示,整个 Door 初始隐藏。show_door 命令激活后显示。")]
|
||||
[Tooltip("Door 根节点。show_door / listen_view 时激活;内含 background、海浪等,不含「可见近景门框」。")]
|
||||
[SerializeField] private Transform doorRoot;
|
||||
|
||||
[Header("Door Sprites")]
|
||||
[SerializeField] private SpriteRenderer glassSea;
|
||||
[SerializeField] private SpriteRenderer codeSea;
|
||||
[SerializeField] private SpriteRenderer flashOverlay;
|
||||
[Tooltip("仅放门的静态 background,不要把 glassSea/codeSea/overlay 拖进来。")]
|
||||
[Tooltip("门后 HDR 白底等。不要把 glassSea/codeSea/overlay 拖进来。")]
|
||||
[SerializeField] private Renderer[] backgroundRenderers;
|
||||
[Tooltip("场景 Frame:默认 inactive,勿当作近景可见门框。可与 terminalShapeReference 共用。")]
|
||||
[SerializeField] private Renderer[] frameRenderers;
|
||||
[SerializeField] private Sprite whiteDoorFrameSprite;
|
||||
[SerializeField] private Sprite seaDoorFrameSprite;
|
||||
@@ -48,12 +59,13 @@ namespace AibisDream
|
||||
[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 Ease morphEase = Ease.OutBack;
|
||||
[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;
|
||||
[Tooltip("变形前停顿,稍长一点更有「蓄力→啪」的顿挫。")]
|
||||
[SerializeField] private float preMorphHoldDuration = 0.22f;
|
||||
[SerializeField] private float postMorphBootDelay = 0.06f;
|
||||
[SerializeField] private float referenceSnapMorphProgress = 0.08f;
|
||||
[SerializeField] private Vector2 referenceSnapPadding = new(0.18f, 0.18f);
|
||||
[Tooltip("终端变形时 sceneBlackOverlay 淡入目标透明度(背景压暗)")]
|
||||
@@ -63,6 +75,12 @@ namespace AibisDream
|
||||
[SerializeField] private float blackoutFadeOutDuration = 0.12f;
|
||||
[Tooltip("变形为终端开始时并行播放的 Timeline(TimelineCenter 名称,如 姐姐/头痛;留空则不播)")]
|
||||
[SerializeField] private string morphStartTimelineName = "姐姐/Glitch";
|
||||
[Tooltip("门框→终端变形时长。偏短配合 OutBack 更容易做出顿挫。")]
|
||||
[SerializeField, Min(0.05f)] private float morphDuration = 0.45f;
|
||||
|
||||
[Header("Ambient")]
|
||||
[Tooltip("变门/终端时立刻关掉的场景背景层(如星空)。须在 HideLargeFrame 之前关掉,否则大图一收会穿帮。")]
|
||||
[SerializeField] private GameObject[] hideOnTerminal;
|
||||
|
||||
private Transform _codeSeaTransform;
|
||||
private Vector3 _codeSeaOriginalLocalPosition;
|
||||
@@ -78,6 +96,7 @@ namespace AibisDream
|
||||
private string _glassSeaCurrentStateName;
|
||||
private MaterialPropertyBlock _codeSeaPropertyBlock;
|
||||
private bool _codeSeaPlaybackActive;
|
||||
private Coroutine _closeupCoroutine;
|
||||
private static readonly int WaveStartTimeId = Shader.PropertyToID("_WaveStartTime");
|
||||
private static readonly int WavePhaseId = Shader.PropertyToID("_WavePhase");
|
||||
private static readonly int WaveSpeedId = Shader.PropertyToID("_WaveSpeed");
|
||||
@@ -120,7 +139,8 @@ namespace AibisDream
|
||||
glassSea.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
SetDoorFrameSprite(hasSea: false);
|
||||
SyncSceneFrameSprite(hasSea: false);
|
||||
KeepSceneFrameInactive();
|
||||
|
||||
if (codeSea != null)
|
||||
{
|
||||
@@ -173,7 +193,8 @@ namespace AibisDream
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 进入“仔细听”的门近景。首次是白底门,之后直接显示门后的金色海浪。
|
||||
/// 门近景:大图显示白/金色门框 + Door 内 background/海浪。
|
||||
/// Yarn 无需再先 show_large_sprite「门框特写」(会被本命令覆盖)。
|
||||
/// </summary>
|
||||
public IEnumerator ShowListenView(bool showSea, float duration = 0f)
|
||||
{
|
||||
@@ -181,8 +202,7 @@ namespace AibisDream
|
||||
EnsureDoorBaseVisible(showSea);
|
||||
ResetCodeSeaImmediate();
|
||||
ResetTerminalImmediate();
|
||||
|
||||
Day2SleepPresentationController.Instance?.SetLargePresentationImmediate(1f, 1f);
|
||||
ShowLargeFrame(hasSea: showSea, scale: 1f, alpha: 1f);
|
||||
|
||||
if (showSea && glassSea != null)
|
||||
{
|
||||
@@ -261,6 +281,7 @@ namespace AibisDream
|
||||
/// </summary>
|
||||
public IEnumerator TweenListenCloseup(float scale, float alpha, float duration)
|
||||
{
|
||||
StopCloseupCoroutine();
|
||||
var presentation = Day2SleepPresentationController.Instance;
|
||||
if (presentation == null)
|
||||
yield break;
|
||||
@@ -268,13 +289,43 @@ namespace AibisDream
|
||||
yield return presentation.AnimateLargePresentation(scale, alpha, duration);
|
||||
}
|
||||
|
||||
/// <summary>异步近景;再次调用 set/tween/show_framed 时会取消上一次。</summary>
|
||||
public void TweenListenCloseupAsync(float scale, float alpha, float duration)
|
||||
{
|
||||
StopCloseupCoroutine();
|
||||
if (Day2SleepPresentationController.Instance == null)
|
||||
return;
|
||||
|
||||
_closeupCoroutine = StartCoroutine(TweenListenCloseupRoutine(scale, alpha, duration));
|
||||
}
|
||||
|
||||
public void SetListenCloseup(float scale, float alpha)
|
||||
{
|
||||
StopCloseupCoroutine();
|
||||
Day2SleepPresentationController.Instance?.SetLargePresentationImmediate(scale, alpha);
|
||||
}
|
||||
|
||||
private IEnumerator TweenListenCloseupRoutine(float scale, float alpha, float duration)
|
||||
{
|
||||
var presentation = Day2SleepPresentationController.Instance;
|
||||
if (presentation == null)
|
||||
yield break;
|
||||
yield return presentation.AnimateLargePresentation(scale, alpha, duration);
|
||||
_closeupCoroutine = null;
|
||||
}
|
||||
|
||||
private void StopCloseupCoroutine()
|
||||
{
|
||||
if (_closeupCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_closeupCoroutine);
|
||||
_closeupCoroutine = null;
|
||||
}
|
||||
Day2SleepPresentationController.Instance?.StopLargePresentationAnimation();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全白遮罩下调用的瞬时状态切换:隐藏门底和门框,仅显示全屏金色海浪。
|
||||
/// 全白时:隐藏门底与大图门框,仅全屏金色海浪。
|
||||
/// </summary>
|
||||
public void ShowGlassSeaFullscreen()
|
||||
{
|
||||
@@ -282,23 +333,25 @@ namespace AibisDream
|
||||
doorRoot.gameObject.SetActive(true);
|
||||
|
||||
SetBackgroundVisible(false);
|
||||
SetFrameVisible(false);
|
||||
HideLargeFrame();
|
||||
ResetCodeSeaImmediate();
|
||||
EnsureGlassSeaVisible(glassSeaStateName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全白遮罩下调用的瞬时状态切换:恢复门底和门框,金色海浪留在门后。
|
||||
/// 全白时:恢复大图门框,金色海浪留在门后。
|
||||
/// </summary>
|
||||
public void ShowGlassSeaFramed()
|
||||
{
|
||||
StopCloseupCoroutine();
|
||||
ResetCodeSeaImmediate();
|
||||
EnsureDoorBaseVisible(hasSea: true);
|
||||
ShowLargeFrame(hasSea: true, scale: 1f, alpha: 1f);
|
||||
EnsureGlassSeaVisible(glassSeaFramedStateName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全白时切到无门的全屏 CodeSea;状态职责与 ShowGlassSeaFullscreen 一致。
|
||||
/// 全白时:隐藏门底与大图门框,全屏 CodeSea。
|
||||
/// </summary>
|
||||
public void ShowCodeSeaFullscreen()
|
||||
{
|
||||
@@ -308,20 +361,23 @@ namespace AibisDream
|
||||
var restartPlayback = _presentationPhase != DoorPresentationPhase.CodeSea
|
||||
|| !_codeSeaPlaybackActive;
|
||||
SetBackgroundVisible(false);
|
||||
SetFrameVisible(false);
|
||||
HideLargeFrame();
|
||||
ResetGlassSeaImmediate();
|
||||
EnsureCodeSeaVisible(restartPlayback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全白时恢复门框,CodeSea 保持显示在门内。
|
||||
/// 全白时:恢复大图白色门框(scale=1),CodeSea 留在门内。
|
||||
/// 会取消进行中的 tween_door_closeup_async,避免停在 4x 放大。
|
||||
/// </summary>
|
||||
public void ShowCodeSeaFramed()
|
||||
{
|
||||
var restartPlayback = _presentationPhase != DoorPresentationPhase.CodeSea
|
||||
|| !_codeSeaPlaybackActive;
|
||||
StopCloseupCoroutine();
|
||||
ResetGlassSeaImmediate();
|
||||
EnsureDoorBaseVisible(hasSea: false);
|
||||
ShowLargeFrame(hasSea: false, scale: 1f, alpha: 1f);
|
||||
EnsureCodeSeaFramedVisible(restartPlayback);
|
||||
}
|
||||
|
||||
@@ -362,7 +418,7 @@ namespace AibisDream
|
||||
_presentationPhase = DoorPresentationPhase.CodeSea;
|
||||
|
||||
if (frameHidePhase == FrameHidePhase.OnCodeSea)
|
||||
SetFrameVisible(false);
|
||||
HideLargeFrame();
|
||||
|
||||
var seq = DOTween.Sequence();
|
||||
seq.Append(codeSea.DOFade(1f, Mathf.Max(0f, duration)).SetEase(Ease.InOutSine));
|
||||
@@ -408,6 +464,9 @@ namespace AibisDream
|
||||
{
|
||||
StopAllEffects();
|
||||
|
||||
// 先关星空等远景层,再收大图;否则 D2S门 一隐星空会露出来穿帮。
|
||||
HideAmbientForTerminal();
|
||||
|
||||
if (terminalUI != null)
|
||||
{
|
||||
terminalUI.ResetVisualState();
|
||||
@@ -422,7 +481,7 @@ namespace AibisDream
|
||||
}
|
||||
|
||||
SetBackgroundVisible(false);
|
||||
SetFrameVisible(false);
|
||||
HideLargeFrame();
|
||||
|
||||
if (glassSea != null)
|
||||
{
|
||||
@@ -454,11 +513,12 @@ namespace AibisDream
|
||||
if (terminalUI != null)
|
||||
{
|
||||
float morph = 0f;
|
||||
// OutBack 带轻微过冲,配合变短的 morphDuration,落地更有顿挫。
|
||||
var morphTween = DOTween.To(() => morph, v =>
|
||||
{
|
||||
morph = v;
|
||||
terminalUI.MorphProgress = v;
|
||||
}, 1f, 0.82f).SetEase(morphEase);
|
||||
}, 1f, Mathf.Max(0.05f, morphDuration)).SetEase(morphEase);
|
||||
|
||||
if (!string.IsNullOrEmpty(morphStartTimelineName))
|
||||
TimelineCenter.Instance.PlayTimeline(morphStartTimelineName);
|
||||
@@ -467,6 +527,7 @@ namespace AibisDream
|
||||
sceneBlackOverlay.DOFade(blackoutAlpha, Mathf.Max(0.01f, blackoutFadeInDuration));
|
||||
|
||||
yield return morphTween.WaitForCompletion();
|
||||
terminalUI.MorphProgress = 1f;
|
||||
}
|
||||
|
||||
ResetDoorShellScale();
|
||||
@@ -507,6 +568,7 @@ namespace AibisDream
|
||||
|
||||
public void StopAllEffects()
|
||||
{
|
||||
StopCloseupCoroutine();
|
||||
DOTween.Kill(glassSea);
|
||||
DOTween.Kill(_codeSeaTransform);
|
||||
DOTween.Kill(codeSea);
|
||||
@@ -527,8 +589,8 @@ namespace AibisDream
|
||||
ResetListenTransitionImmediate();
|
||||
HideDoor();
|
||||
SetBackgroundVisible(true);
|
||||
SetFrameVisible(true);
|
||||
SetDoorFrameSprite(hasSea: false);
|
||||
SyncSceneFrameSprite(hasSea: false);
|
||||
KeepSceneFrameInactive();
|
||||
ResetDoorShellScale();
|
||||
|
||||
if (glassSea != null)
|
||||
@@ -566,7 +628,7 @@ namespace AibisDream
|
||||
if (sceneBlackOverlay != null)
|
||||
sceneBlackOverlay.SetAlpha(0);
|
||||
|
||||
Day2SleepPresentationController.Instance?.HideLargePresentationImmediate();
|
||||
HideLargeFrame();
|
||||
|
||||
_presentationPhase = DoorPresentationPhase.Hidden;
|
||||
}
|
||||
@@ -575,17 +637,77 @@ namespace AibisDream
|
||||
|
||||
#region Presentation State
|
||||
|
||||
/// <summary>
|
||||
/// 打开 Door 根与门后背景;不同时显示场景 Frame(近景门框只走大图)。
|
||||
/// </summary>
|
||||
private void EnsureDoorBaseVisible(bool hasSea = false)
|
||||
{
|
||||
if (doorRoot != null)
|
||||
doorRoot.gameObject.SetActive(true);
|
||||
|
||||
// 梦境「遮罩」是纯黑全屏;不关会从门框透明洞透出黑底,盖住 HDR 白光。
|
||||
SpriteShowcase.Instance?.HideBackgroundImmediate();
|
||||
SetBackgroundVisible(true);
|
||||
SetFrameVisible(true);
|
||||
SetDoorFrameSprite(hasSea);
|
||||
KeepSceneFrameInactive();
|
||||
SyncSceneFrameSprite(hasSea);
|
||||
ResetDoorShellScale();
|
||||
}
|
||||
|
||||
/// <summary>可见门框:大图贴白/金色门框并设缩放透明度。</summary>
|
||||
private void ShowLargeFrame(bool hasSea, float scale, float alpha)
|
||||
{
|
||||
var targetSprite = hasSea ? seaDoorFrameSprite : whiteDoorFrameSprite;
|
||||
SyncSceneFrameSprite(hasSea);
|
||||
var presentation = Day2SleepPresentationController.Instance;
|
||||
if (presentation == null) return;
|
||||
if (targetSprite != null)
|
||||
presentation.SetLargeSpriteImmediate(targetSprite);
|
||||
presentation.SetLargePresentationImmediate(scale, alpha);
|
||||
}
|
||||
|
||||
private void HideLargeFrame()
|
||||
{
|
||||
Day2SleepPresentationController.Instance?.HideLargePresentationImmediate();
|
||||
}
|
||||
|
||||
/// <summary>只同步场景 Frame 贴图,不激活(供 morph 参考一致)。</summary>
|
||||
private void SyncSceneFrameSprite(bool hasSea)
|
||||
{
|
||||
var targetSprite = hasSea ? seaDoorFrameSprite : whiteDoorFrameSprite;
|
||||
if (targetSprite == null || frameRenderers == null) return;
|
||||
|
||||
foreach (var frameRenderer in frameRenderers)
|
||||
{
|
||||
if (frameRenderer is SpriteRenderer spriteRenderer)
|
||||
spriteRenderer.sprite = targetSprite;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景 Frame 必须保持 inactive。其 localScale≈0.73,若被激活会叠出一层「缩小门框」。
|
||||
/// </summary>
|
||||
private void KeepSceneFrameInactive()
|
||||
{
|
||||
if (frameRenderers == null) return;
|
||||
foreach (var frameRenderer in frameRenderers)
|
||||
{
|
||||
if (frameRenderer == null) continue;
|
||||
if (frameRenderer.gameObject.activeSelf)
|
||||
frameRenderer.gameObject.SetActive(false);
|
||||
frameRenderer.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void HideAmbientForTerminal()
|
||||
{
|
||||
if (hideOnTerminal == null) return;
|
||||
foreach (var go in hideOnTerminal)
|
||||
{
|
||||
if (go != null && go.activeSelf)
|
||||
go.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetGlassSeaImmediate()
|
||||
{
|
||||
DOTween.Kill(glassSea);
|
||||
@@ -832,35 +954,6 @@ namespace AibisDream
|
||||
|
||||
#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;
|
||||
|
||||
Reference in New Issue
Block a user