feat(dream): 完善梦境门与星空聚焦演出

This commit is contained in:
2026-07-16 17:52:34 +08:00
parent 0039beb5f9
commit ea95111f2a
18 changed files with 951 additions and 54 deletions
+295 -13
View File
@@ -4,6 +4,7 @@ using AibisDream.UI;
using AibisDream.Utility;
using DG.Tweening;
using UnityEngine;
using UnityEngine.Video;
namespace AibisDream
{
@@ -22,6 +23,11 @@ namespace AibisDream
[SerializeField] private Renderer[] frameRenderers;
[SerializeField] private SpriteRenderer sceneBlackOverlay;
[Header("Door Sea Memory")]
[SerializeField] private VideoClip glassSeaVideoClip;
[SerializeField] private RenderTexture glassSeaTargetTexture;
[SerializeField, Min(0.5f)] private float glassSeaPrepareTimeout = 5f;
[Header("Terminal(挂在 Door 子节点上即可,无需 Canvas")]
[SerializeField] private DreamTerminalUI terminalUI;
@@ -56,6 +62,14 @@ namespace AibisDream
private Vector3 _terminalUiOriginalLocalPosition;
private Transform _doorShellTransform;
private Vector3 _doorShellOriginalScale;
private VideoPlayer _glassSeaVideoPlayer;
private Material _glassSeaMaterial;
private bool _glassSeaVideoPrepared;
private bool _glassSeaVideoFailed;
private bool _glassSeaFrameReady;
private DoorPresentationPhase _presentationPhase = DoorPresentationPhase.Hidden;
private static readonly int UseVideoId = Shader.PropertyToID("_UseVideo");
private enum FrameHidePhase
{
@@ -64,6 +78,15 @@ namespace AibisDream
OnTerminal
}
private enum DoorPresentationPhase
{
Hidden,
Door,
GlassSea,
CodeSea,
Terminal
}
public override void OnSingletonInit()
{
if (doorRoot != null)
@@ -75,10 +98,14 @@ namespace AibisDream
if (glassSea != null)
{
_glassSeaMaterial = glassSea.material;
SetGlassSeaVideoVisible(false);
glassSea.SetAlpha(0);
glassSea.gameObject.SetActive(false);
}
InitializeGlassSeaVideo();
if (codeSea != null)
{
codeSea.SetAlpha(0);
@@ -108,19 +135,33 @@ namespace AibisDream
_terminalUiOriginalScale = _terminalUiTransform.localScale;
_terminalUiOriginalLocalPosition = _terminalUiTransform.localPosition;
}
_presentationPhase = DoorPresentationPhase.Hidden;
}
public override void OnSingletonDestroy()
{
if (_glassSeaVideoPlayer != null)
{
_glassSeaVideoPlayer.prepareCompleted -= OnGlassSeaVideoPrepared;
_glassSeaVideoPlayer.frameReady -= OnGlassSeaVideoFrameReady;
_glassSeaVideoPlayer.errorReceived -= OnGlassSeaVideoError;
}
base.OnSingletonDestroy();
}
/// <summary>
/// 显示整个门(仅 background + FrameGlassSea/CodeSea 保持隐藏直到各自命令调用)
/// 显示普通门。该命令是一个完整状态切换,会清掉之前残留的海景、代码海浪和终端。
/// </summary>
public IEnumerator ShowDoor(float duration = 0f)
{
if (doorRoot != null)
doorRoot.gameObject.SetActive(true);
SetBackgroundVisible(true);
SetFrameVisible(true);
ResetDoorShellScale();
StopAllEffects();
EnsureDoorBaseVisible();
ResetGlassSeaImmediate(stopVideo: true);
ResetCodeSeaImmediate();
ResetTerminalImmediate();
_presentationPhase = DoorPresentationPhase.Door;
if (duration > 0f)
yield return new WaitForSeconds(duration);
@@ -139,18 +180,48 @@ namespace AibisDream
public IEnumerator ShowGlassSea(float duration = 2f)
{
if (glassSea == null)
yield break;
StopAllEffects();
EnsureDoorBaseVisible();
ResetCodeSeaImmediate();
ResetTerminalImmediate();
if (_presentationPhase != DoorPresentationPhase.GlassSea || !glassSea.gameObject.activeSelf)
glassSea.SetAlpha(0f);
glassSea.gameObject.SetActive(true);
yield return glassSea.DOFade(1f, duration)
_presentationPhase = DoorPresentationPhase.GlassSea;
yield return EnsureGlassSeaVideoPlaying();
yield return glassSea.DOFade(1f, Mathf.Max(0f, duration))
.SetEase(Ease.InOutSine)
.WaitForCompletion();
}
public IEnumerator HideGlassSea(float duration = 1f)
{
yield return glassSea.DOFade(0f, duration)
.SetEase(Ease.InOutSine)
.WaitForCompletion();
if (glassSea == null)
{
PauseGlassSeaVideo();
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);
glassSea.gameObject.SetActive(false);
PauseGlassSeaVideo();
if (_presentationPhase == DoorPresentationPhase.GlassSea)
_presentationPhase = DoorPresentationPhase.Door;
}
#endregion
@@ -159,21 +230,39 @@ namespace AibisDream
public IEnumerator ShowCodeSea(float duration = 2f)
{
if (codeSea == null)
yield break;
StopAllEffects();
EnsureDoorBaseVisible();
ResetTerminalImmediate();
codeSea.color = _codeSeaOriginalColor;
if (_codeSeaTransform != null)
_codeSeaTransform.localScale = _codeSeaOriginalScale;
codeSea.SetAlpha(0f);
codeSea.gameObject.SetActive(true);
_presentationPhase = DoorPresentationPhase.CodeSea;
if (frameHidePhase == FrameHidePhase.OnCodeSea)
SetFrameVisible(false);
var seq = DOTween.Sequence();
seq.Append(codeSea.DOFade(1f, duration).SetEase(Ease.InOutSine));
seq.Append(codeSea.DOFade(1f, Mathf.Max(0f, duration)).SetEase(Ease.InOutSine));
if (glassSea != null && glassSea.gameObject.activeSelf)
{
seq.Join(glassSea.DOFade(0f, duration * 0.7f).SetEase(Ease.InSine));
seq.Join(glassSea.DOFade(0f, Mathf.Max(0f, duration) * 0.7f).SetEase(Ease.InSine));
}
yield return seq.WaitForCompletion();
if (glassSea != null)
{
glassSea.SetAlpha(0f);
glassSea.gameObject.SetActive(false);
}
StopGlassSeaVideo();
}
#endregion
@@ -182,6 +271,7 @@ namespace AibisDream
public IEnumerator ShowTerminal()
{
_presentationPhase = DoorPresentationPhase.Terminal;
yield return AnimateDoorToTerminal();
if (terminalUI != null)
{
@@ -214,6 +304,8 @@ namespace AibisDream
if (glassSea != null)
glassSea.gameObject.SetActive(false);
StopGlassSeaVideo();
if (codeSea != null)
codeSea.gameObject.SetActive(false);
@@ -288,6 +380,7 @@ namespace AibisDream
public void StopAllEffects()
{
DOTween.Kill(glassSea);
DOTween.Kill(_codeSeaTransform);
DOTween.Kill(codeSea);
DOTween.Kill(_terminalUiTransform);
@@ -314,6 +407,8 @@ namespace AibisDream
glassSea.gameObject.SetActive(false);
}
StopGlassSeaVideo();
if (codeSea != null)
{
codeSea.color = _codeSeaOriginalColor;
@@ -340,6 +435,193 @@ namespace AibisDream
if (sceneBlackOverlay != null)
sceneBlackOverlay.SetAlpha(0);
_presentationPhase = DoorPresentationPhase.Hidden;
}
#endregion
#region Presentation State
private void EnsureDoorBaseVisible()
{
if (doorRoot != null)
doorRoot.gameObject.SetActive(true);
SetBackgroundVisible(true);
SetFrameVisible(true);
ResetDoorShellScale();
}
private void ResetGlassSeaImmediate(bool stopVideo)
{
DOTween.Kill(glassSea);
if (glassSea != null)
{
glassSea.SetAlpha(0f);
glassSea.gameObject.SetActive(false);
}
if (stopVideo)
StopGlassSeaVideo();
else
PauseGlassSeaVideo();
}
private void ResetCodeSeaImmediate()
{
DOTween.Kill(_codeSeaTransform);
DOTween.Kill(codeSea);
if (codeSea == null)
return;
codeSea.color = _codeSeaOriginalColor;
codeSea.SetAlpha(0f);
codeSea.gameObject.SetActive(false);
if (_codeSeaTransform != null)
_codeSeaTransform.localScale = _codeSeaOriginalScale;
}
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 Video
private void InitializeGlassSeaVideo()
{
if (glassSeaVideoClip == null || glassSeaTargetTexture == null)
{
SetGlassSeaVideoVisible(false);
return;
}
_glassSeaVideoPlayer = GetComponent<VideoPlayer>();
if (_glassSeaVideoPlayer == null)
_glassSeaVideoPlayer = gameObject.AddComponent<VideoPlayer>();
_glassSeaVideoPlayer.playOnAwake = false;
_glassSeaVideoPlayer.source = VideoSource.VideoClip;
_glassSeaVideoPlayer.clip = glassSeaVideoClip;
_glassSeaVideoPlayer.renderMode = VideoRenderMode.RenderTexture;
_glassSeaVideoPlayer.targetTexture = glassSeaTargetTexture;
_glassSeaVideoPlayer.audioOutputMode = VideoAudioOutputMode.None;
_glassSeaVideoPlayer.isLooping = true;
_glassSeaVideoPlayer.skipOnDrop = true;
_glassSeaVideoPlayer.waitForFirstFrame = true;
_glassSeaVideoPlayer.sendFrameReadyEvents = true;
_glassSeaVideoPlayer.prepareCompleted += OnGlassSeaVideoPrepared;
_glassSeaVideoPlayer.frameReady += OnGlassSeaVideoFrameReady;
_glassSeaVideoPlayer.errorReceived += OnGlassSeaVideoError;
_glassSeaVideoPrepared = false;
_glassSeaVideoFailed = false;
_glassSeaFrameReady = false;
SetGlassSeaVideoVisible(false);
_glassSeaVideoPlayer.Prepare();
}
private IEnumerator EnsureGlassSeaVideoPlaying()
{
if (_glassSeaVideoPlayer == null || _glassSeaVideoFailed)
{
SetGlassSeaVideoVisible(false);
yield break;
}
if (!_glassSeaVideoPrepared && !_glassSeaVideoPlayer.isPrepared)
{
float deadline = Time.realtimeSinceStartup + glassSeaPrepareTimeout;
while (!_glassSeaVideoPrepared && !_glassSeaVideoFailed && Time.realtimeSinceStartup < deadline)
yield return null;
if (!_glassSeaVideoPrepared && !_glassSeaVideoPlayer.isPrepared)
{
_glassSeaVideoFailed = true;
_glassSeaVideoPlayer.Stop();
SetGlassSeaVideoVisible(false);
Debug.LogWarning("[DreamDoorSystem] 海景视频准备超时,回退到静态海报。", this);
yield break;
}
}
_glassSeaVideoPrepared = true;
if (!_glassSeaVideoPlayer.isPlaying)
_glassSeaVideoPlayer.Play();
if (!_glassSeaFrameReady)
{
float firstFrameDeadline = Time.realtimeSinceStartup + 1f;
while (!_glassSeaFrameReady && !_glassSeaVideoFailed && Time.realtimeSinceStartup < firstFrameDeadline)
yield return null;
}
}
private void PauseGlassSeaVideo()
{
if (_glassSeaVideoPlayer != null && _glassSeaVideoPlayer.isPlaying)
_glassSeaVideoPlayer.Pause();
}
private void StopGlassSeaVideo()
{
if (_glassSeaVideoPlayer != null)
_glassSeaVideoPlayer.Stop();
_glassSeaFrameReady = false;
SetGlassSeaVideoVisible(false);
}
private void OnGlassSeaVideoPrepared(VideoPlayer source)
{
_glassSeaVideoPrepared = true;
_glassSeaVideoFailed = false;
}
private void OnGlassSeaVideoFrameReady(VideoPlayer source, long frameIndex)
{
_glassSeaFrameReady = true;
SetGlassSeaVideoVisible(true);
}
private void OnGlassSeaVideoError(VideoPlayer source, string message)
{
_glassSeaVideoFailed = true;
_glassSeaVideoPrepared = false;
_glassSeaFrameReady = false;
SetGlassSeaVideoVisible(false);
Debug.LogWarning($"[DreamDoorSystem] 海景视频播放失败,回退到静态海报:{message}", this);
}
private void SetGlassSeaVideoVisible(bool visible)
{
if (_glassSeaMaterial != null && _glassSeaMaterial.HasProperty(UseVideoId))
_glassSeaMaterial.SetFloat(UseVideoId, visible ? 1f : 0f);
}
#endregion