feat(scene-mgmt): 重构梦境门海浪切换演出
This commit is contained in:
@@ -4,7 +4,6 @@ using AibisDream.UI;
|
||||
using AibisDream.Utility;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Video;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
@@ -21,12 +20,23 @@ namespace AibisDream
|
||||
[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 Memory")]
|
||||
[SerializeField] private VideoClip glassSeaVideoClip;
|
||||
[SerializeField] private RenderTexture glassSeaTargetTexture;
|
||||
[SerializeField, Min(0.5f)] private float glassSeaPrepareTimeout = 5f;
|
||||
[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;
|
||||
@@ -55,6 +65,7 @@ namespace AibisDream
|
||||
[SerializeField] private string morphStartTimelineName = "姐姐/Glitch";
|
||||
|
||||
private Transform _codeSeaTransform;
|
||||
private Vector3 _codeSeaOriginalLocalPosition;
|
||||
private Vector3 _codeSeaOriginalScale;
|
||||
private Color _codeSeaOriginalColor;
|
||||
private Transform _terminalUiTransform;
|
||||
@@ -62,15 +73,16 @@ 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 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 static readonly int UseVideoId = Shader.PropertyToID("_UseVideo");
|
||||
|
||||
private enum FrameHidePhase
|
||||
{
|
||||
Never,
|
||||
@@ -98,21 +110,26 @@ namespace AibisDream
|
||||
|
||||
if (glassSea != null)
|
||||
{
|
||||
_glassSeaMaterial = glassSea.material;
|
||||
SetGlassSeaVideoVisible(false);
|
||||
_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);
|
||||
}
|
||||
|
||||
InitializeGlassSeaVideo();
|
||||
SetDoorFrameSprite(hasSea: false);
|
||||
|
||||
if (codeSea != null)
|
||||
{
|
||||
codeSea.SetAlpha(0);
|
||||
codeSea.gameObject.SetActive(false);
|
||||
_codeSeaTransform = codeSea.transform;
|
||||
_codeSeaOriginalLocalPosition = _codeSeaTransform.localPosition;
|
||||
_codeSeaOriginalScale = _codeSeaTransform.localScale;
|
||||
_codeSeaOriginalColor = codeSea.color;
|
||||
codeSea.SetAlpha(0);
|
||||
codeSea.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
if (flashOverlay != null)
|
||||
@@ -139,26 +156,14 @@ namespace AibisDream
|
||||
_presentationPhase = DoorPresentationPhase.Hidden;
|
||||
}
|
||||
|
||||
public override void OnSingletonDestroy()
|
||||
{
|
||||
if (_glassSeaVideoPlayer != null)
|
||||
{
|
||||
_glassSeaVideoPlayer.prepareCompleted -= OnGlassSeaVideoPrepared;
|
||||
_glassSeaVideoPlayer.frameReady -= OnGlassSeaVideoFrameReady;
|
||||
_glassSeaVideoPlayer.errorReceived -= OnGlassSeaVideoError;
|
||||
}
|
||||
|
||||
base.OnSingletonDestroy();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示普通门。该命令是一个完整状态切换,会清掉之前残留的海景、代码海浪和终端。
|
||||
/// </summary>
|
||||
public IEnumerator ShowDoor(float duration = 0f)
|
||||
{
|
||||
StopAllEffects();
|
||||
EnsureDoorBaseVisible();
|
||||
ResetGlassSeaImmediate(stopVideo: true);
|
||||
EnsureDoorBaseVisible(hasSea: false);
|
||||
ResetGlassSeaImmediate();
|
||||
ResetCodeSeaImmediate();
|
||||
ResetTerminalImmediate();
|
||||
_presentationPhase = DoorPresentationPhase.Door;
|
||||
@@ -167,6 +172,36 @@ namespace AibisDream
|
||||
yield return new WaitForSeconds(duration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 进入“仔细听”的门近景。首次是白底门,之后直接显示门后的金色海浪。
|
||||
/// </summary>
|
||||
public IEnumerator ShowListenView(bool showSea, float duration = 0f)
|
||||
{
|
||||
StopAllEffects();
|
||||
EnsureDoorBaseVisible(showSea);
|
||||
ResetCodeSeaImmediate();
|
||||
ResetTerminalImmediate();
|
||||
|
||||
var showcase = SpriteShowcase.Instance;
|
||||
if (showcase != null)
|
||||
showcase.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>
|
||||
@@ -184,16 +219,16 @@ namespace AibisDream
|
||||
yield break;
|
||||
|
||||
StopAllEffects();
|
||||
EnsureDoorBaseVisible();
|
||||
EnsureDoorBaseVisible(hasSea: true);
|
||||
ResetCodeSeaImmediate();
|
||||
ResetTerminalImmediate();
|
||||
|
||||
if (_presentationPhase != DoorPresentationPhase.GlassSea || !glassSea.gameObject.activeSelf)
|
||||
glassSea.SetAlpha(0f);
|
||||
|
||||
glassSea.gameObject.SetActive(true);
|
||||
StartGlassSeaAnimation(glassSeaFramedStateName);
|
||||
_presentationPhase = DoorPresentationPhase.GlassSea;
|
||||
yield return EnsureGlassSeaVideoPlaying();
|
||||
|
||||
yield return glassSea.DOFade(1f, Mathf.Max(0f, duration))
|
||||
.SetEase(Ease.InOutSine)
|
||||
.WaitForCompletion();
|
||||
@@ -203,7 +238,6 @@ namespace AibisDream
|
||||
{
|
||||
if (glassSea == null)
|
||||
{
|
||||
PauseGlassSeaVideo();
|
||||
yield break;
|
||||
}
|
||||
|
||||
@@ -217,13 +251,92 @@ namespace AibisDream
|
||||
}
|
||||
|
||||
glassSea.SetAlpha(0f);
|
||||
StopGlassSeaAnimation(resetToFirstFrame: true);
|
||||
glassSea.gameObject.SetActive(false);
|
||||
PauseGlassSeaVideo();
|
||||
|
||||
if (_presentationPhase == DoorPresentationPhase.GlassSea)
|
||||
_presentationPhase = DoorPresentationPhase.Door;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 只调整近景门图片的缩放与透明度,不包含白幕、Bloom 或海浪状态切换。
|
||||
/// </summary>
|
||||
public IEnumerator TweenListenCloseup(float scale, float alpha, float duration)
|
||||
{
|
||||
var showcase = SpriteShowcase.Instance;
|
||||
if (showcase == null)
|
||||
yield break;
|
||||
|
||||
yield return showcase.AnimateLargePresentation(scale, alpha, duration);
|
||||
}
|
||||
|
||||
public void SetListenCloseup(float scale, float alpha)
|
||||
{
|
||||
SpriteShowcase.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);
|
||||
|
||||
SpriteShowcase.Instance?.ResetLargePresentationImmediate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Phase 2: CodeSea 浮现 (GlassSea 交叉淡出)
|
||||
@@ -233,15 +346,21 @@ namespace AibisDream
|
||||
if (codeSea == null)
|
||||
yield break;
|
||||
|
||||
var restartPlayback = _presentationPhase != DoorPresentationPhase.CodeSea
|
||||
|| !_codeSeaPlaybackActive;
|
||||
StopAllEffects();
|
||||
EnsureDoorBaseVisible();
|
||||
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)
|
||||
@@ -259,10 +378,10 @@ namespace AibisDream
|
||||
if (glassSea != null)
|
||||
{
|
||||
glassSea.SetAlpha(0f);
|
||||
StopGlassSeaAnimation(resetToFirstFrame: true);
|
||||
glassSea.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
StopGlassSeaVideo();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -302,12 +421,16 @@ namespace AibisDream
|
||||
SetFrameVisible(false);
|
||||
|
||||
if (glassSea != null)
|
||||
{
|
||||
StopGlassSeaAnimation(resetToFirstFrame: true);
|
||||
glassSea.gameObject.SetActive(false);
|
||||
|
||||
StopGlassSeaVideo();
|
||||
}
|
||||
|
||||
if (codeSea != null)
|
||||
{
|
||||
codeSea.gameObject.SetActive(false);
|
||||
ResetCodeSeaPlayback();
|
||||
}
|
||||
|
||||
if (flashOverlay != null)
|
||||
flashOverlay.SetAlpha(0f);
|
||||
@@ -387,6 +510,7 @@ namespace AibisDream
|
||||
DOTween.Kill(_doorShellTransform);
|
||||
DOTween.Kill(flashOverlay);
|
||||
DOTween.Kill(sceneBlackOverlay);
|
||||
SpriteShowcase.Instance?.StopLargePresentationAnimation();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -396,19 +520,20 @@ namespace AibisDream
|
||||
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);
|
||||
}
|
||||
|
||||
StopGlassSeaVideo();
|
||||
|
||||
if (codeSea != null)
|
||||
{
|
||||
codeSea.color = _codeSeaOriginalColor;
|
||||
@@ -417,6 +542,7 @@ namespace AibisDream
|
||||
if (_codeSeaTransform != null)
|
||||
_codeSeaTransform.localScale = _codeSeaOriginalScale;
|
||||
}
|
||||
ResetCodeSeaPlayback();
|
||||
|
||||
if (terminalUI != null)
|
||||
{
|
||||
@@ -436,6 +562,12 @@ namespace AibisDream
|
||||
if (sceneBlackOverlay != null)
|
||||
sceneBlackOverlay.SetAlpha(0);
|
||||
|
||||
var showcase = SpriteShowcase.Instance;
|
||||
if (showcase != null)
|
||||
{
|
||||
showcase.HideLargePresentationImmediate();
|
||||
}
|
||||
|
||||
_presentationPhase = DoorPresentationPhase.Hidden;
|
||||
}
|
||||
|
||||
@@ -443,30 +575,44 @@ namespace AibisDream
|
||||
|
||||
#region Presentation State
|
||||
|
||||
private void EnsureDoorBaseVisible()
|
||||
private void EnsureDoorBaseVisible(bool hasSea = false)
|
||||
{
|
||||
if (doorRoot != null)
|
||||
doorRoot.gameObject.SetActive(true);
|
||||
|
||||
SetBackgroundVisible(true);
|
||||
SetFrameVisible(true);
|
||||
SetDoorFrameSprite(hasSea);
|
||||
ResetDoorShellScale();
|
||||
}
|
||||
|
||||
private void ResetGlassSeaImmediate(bool stopVideo)
|
||||
private void ResetGlassSeaImmediate()
|
||||
{
|
||||
DOTween.Kill(glassSea);
|
||||
|
||||
if (glassSea != null)
|
||||
{
|
||||
glassSea.SetAlpha(0f);
|
||||
StopGlassSeaAnimation(resetToFirstFrame: true);
|
||||
glassSea.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (stopVideo)
|
||||
StopGlassSeaVideo();
|
||||
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
|
||||
PauseGlassSeaVideo();
|
||||
FitGlassSeaToCameraCover();
|
||||
|
||||
_presentationPhase = DoorPresentationPhase.GlassSea;
|
||||
}
|
||||
|
||||
private void ResetCodeSeaImmediate()
|
||||
@@ -474,6 +620,8 @@ namespace AibisDream
|
||||
DOTween.Kill(_codeSeaTransform);
|
||||
DOTween.Kill(codeSea);
|
||||
|
||||
ResetCodeSeaPlayback();
|
||||
|
||||
if (codeSea == null)
|
||||
return;
|
||||
|
||||
@@ -482,7 +630,62 @@ namespace AibisDream
|
||||
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()
|
||||
@@ -510,118 +713,121 @@ namespace AibisDream
|
||||
|
||||
#endregion
|
||||
|
||||
#region GlassSea Video
|
||||
#region GlassSea Animation
|
||||
|
||||
private void InitializeGlassSeaVideo()
|
||||
private void StartGlassSeaAnimation(string stateName = null)
|
||||
{
|
||||
if (glassSeaVideoClip == null || glassSeaTargetTexture == null)
|
||||
if (glassSea == null) return;
|
||||
|
||||
stateName = string.IsNullOrEmpty(stateName) ? glassSeaStateName : stateName;
|
||||
|
||||
glassSea.gameObject.SetActive(true);
|
||||
if (glassSeaAnimator != null && glassSeaAnimator.runtimeAnimatorController != null)
|
||||
{
|
||||
SetGlassSeaVideoVisible(false);
|
||||
return;
|
||||
glassSeaAnimator.enabled = true;
|
||||
glassSeaAnimator.Play(stateName, 0, 0f);
|
||||
glassSeaAnimator.Update(0f);
|
||||
_glassSeaCurrentStateName = stateName;
|
||||
}
|
||||
|
||||
_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();
|
||||
FitGlassSeaToCameraCover();
|
||||
}
|
||||
|
||||
private IEnumerator EnsureGlassSeaVideoPlaying()
|
||||
private void StopGlassSeaAnimation(bool resetToFirstFrame)
|
||||
{
|
||||
if (_glassSeaVideoPlayer == null || _glassSeaVideoFailed)
|
||||
if (glassSeaAnimator == null) return;
|
||||
|
||||
if (resetToFirstFrame
|
||||
&& glassSeaAnimator.runtimeAnimatorController != null
|
||||
&& glassSeaAnimator.gameObject.activeInHierarchy)
|
||||
{
|
||||
SetGlassSeaVideoVisible(false);
|
||||
yield break;
|
||||
var stateName = string.IsNullOrEmpty(_glassSeaCurrentStateName)
|
||||
? glassSeaStateName
|
||||
: _glassSeaCurrentStateName;
|
||||
glassSeaAnimator.enabled = true;
|
||||
glassSeaAnimator.Play(stateName, 0, 0f);
|
||||
glassSeaAnimator.Update(0f);
|
||||
}
|
||||
|
||||
if (!_glassSeaVideoPrepared && !_glassSeaVideoPlayer.isPrepared)
|
||||
{
|
||||
float deadline = Time.realtimeSinceStartup + glassSeaPrepareTimeout;
|
||||
while (!_glassSeaVideoPrepared && !_glassSeaVideoFailed && Time.realtimeSinceStartup < deadline)
|
||||
yield return null;
|
||||
glassSeaAnimator.enabled = false;
|
||||
_glassSeaCurrentStateName = null;
|
||||
}
|
||||
|
||||
if (!_glassSeaVideoPrepared && !_glassSeaVideoPlayer.isPrepared)
|
||||
{
|
||||
_glassSeaVideoFailed = true;
|
||||
_glassSeaVideoPlayer.Stop();
|
||||
SetGlassSeaVideoVisible(false);
|
||||
Debug.LogWarning("[DreamDoorSystem] 海景视频准备超时,回退到静态海报。", this);
|
||||
yield break;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
_glassSeaVideoPrepared = true;
|
||||
if (!_glassSeaVideoPlayer.isPlaying)
|
||||
_glassSeaVideoPlayer.Play();
|
||||
var spriteSize = target.sprite.bounds.size;
|
||||
if (spriteSize.x <= 0.001f || spriteSize.y <= 0.001f) return;
|
||||
|
||||
if (!_glassSeaFrameReady)
|
||||
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)
|
||||
{
|
||||
float firstFrameDeadline = Time.realtimeSinceStartup + 1f;
|
||||
while (!_glassSeaFrameReady && !_glassSeaVideoFailed && Time.realtimeSinceStartup < firstFrameDeadline)
|
||||
yield return null;
|
||||
if (candidate != null
|
||||
&& candidate.isActiveAndEnabled
|
||||
&& candidate.gameObject.scene == gameObject.scene)
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
return Camera.main;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -637,6 +843,24 @@ namespace AibisDream
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
SpriteShowcase.Instance?.SetLargeSpriteImmediate(targetSprite);
|
||||
}
|
||||
|
||||
private void SetBackgroundVisible(bool visible)
|
||||
{
|
||||
if (backgroundRenderers == null) return;
|
||||
|
||||
Reference in New Issue
Block a user