feat(dream): 完善梦境门与星空聚焦演出
This commit is contained in:
@@ -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 + Frame,GlassSea/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
|
||||
|
||||
Reference in New Issue
Block a user