From 660f8e24ee822c2f97a02e6c36f1a70d2fcae242 Mon Sep 17 00:00:00 2001 From: bottlefish <781230111@qq.com> Date: Wed, 1 Jul 2026 17:39:24 +0800 Subject: [PATCH] =?UTF-8?q?fix(eye):=20=E4=BF=AE=E5=A4=8D=E8=A7=86?= =?UTF-8?q?=E5=8F=A3=E9=81=AE=E7=BD=A9=E4=B8=8D=E5=8F=AF=E8=A7=81=E5=B9=B6?= =?UTF-8?q?=E9=81=BF=E5=85=8D=E9=81=AE=E6=8C=A1=E7=B3=BB=E7=BB=9F=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 overlay Canvas 设到 UI 层以匹配 UI Camera culling mask - sortingOrder 设为 0,位于 UI Canvas(1) 与 Dialog(3) 之下 - 修正 EnterModule 与 Start 时序,避免模块激活后被误关 Co-authored-by: Cursor --- .../MiniGame/Peipei/Eye/EyeViewportOverlay.cs | 824 +++++++++--------- 1 file changed, 421 insertions(+), 403 deletions(-) diff --git a/Assets/Scripts/MiniGame/Peipei/Eye/EyeViewportOverlay.cs b/Assets/Scripts/MiniGame/Peipei/Eye/EyeViewportOverlay.cs index 4b90a49d7..2256549b0 100644 --- a/Assets/Scripts/MiniGame/Peipei/Eye/EyeViewportOverlay.cs +++ b/Assets/Scripts/MiniGame/Peipei/Eye/EyeViewportOverlay.cs @@ -1,403 +1,421 @@ -using System.Collections; -using DG.Tweening; -using UnityEngine; -using UnityEngine.UI; - -namespace AibisDream -{ - /// - /// Mobile-friendly fullscreen eye viewport: elliptical sight + vertical ellipse blink. - /// - [DisallowMultipleComponent] - [ExecuteAlways] - public class EyeViewportOverlay : MonoBehaviour - { - private const string OverlaySortingLayer = "StartUI"; - private const float OverlayPlaneDistance = 10f; - - private static readonly int EyeWidthId = Shader.PropertyToID("_EyeWidth"); - private static readonly int EyeHeightId = Shader.PropertyToID("_EyeHeight"); - private static readonly int EdgeSoftnessId = Shader.PropertyToID("_EdgeSoftness"); - private static readonly int BlinkOpenId = Shader.PropertyToID("_BlinkOpen"); - - /// 须低于 Persistence 中 UI Canvas(1) 与 Dialog Canvas(3)。 - [SerializeField] private int sortingOrder; - - private Canvas overlayCanvas; - private Image overlayImage; - private Material instanceMaterial; - private EyeVisualSettings activeSettings; - private Coroutine autoBlinkRoutine; - private Tween blinkTween; - private float blinkOpen = 1f; - private bool moduleActive; - private bool editorPreviewActive; - private bool hierarchyReady; - private bool lifecycleReady; - private Coroutine deferPrepareRoutine; - - private void Start() - { - lifecycleReady = true; - if (!Application.isPlaying) - { - return; - } - - PrepareHierarchyIfNeeded(); - SetOverlayActive(false); - } - - private void OnDestroy() - { - StopEffects(); - if (instanceMaterial != null) - { - Destroy(instanceMaterial); - } - } - - public void EnterModule(EyeVisualSettings settings) - { - moduleActive = true; - editorPreviewActive = false; - ShowOverlay(settings); - } - - public void ExitModule() - { - moduleActive = false; - if (editorPreviewActive) - { - return; - } - - StopEffects(); - SetOverlayActive(false); - activeSettings = null; - } - - public void EnterEditorPreview(EyeVisualSettings settings) - { - editorPreviewActive = true; - ShowOverlay(settings); - } - - public void ExitEditorPreview() - { - editorPreviewActive = false; - if (moduleActive) - { - ShowOverlay(activeSettings); - return; - } - - StopEffects(); - SetOverlayActive(false); - activeSettings = null; - } - - public void ApplySettings(EyeVisualSettings settings) - { - if (settings == null) - { - return; - } - - if (!settings.enableViewportOverlay) - { - if (editorPreviewActive) - { - ExitEditorPreview(); - } - else if (moduleActive) - { - ExitModule(); - } - - return; - } - - PrepareHierarchyIfNeeded(); - if (instanceMaterial == null) - { - return; - } - - activeSettings = settings; - PushViewportParams(settings); - SetBlinkOpen(blinkOpen); - - if (!overlayCanvas.gameObject.activeInHierarchy) - { - return; - } - - if (settings.autoBlink && Application.isPlaying && (moduleActive || editorPreviewActive)) - { - StartAutoBlink(settings); - } - else - { - StopAutoBlink(); - } - } - - public Tween PlayBlink(float? totalDuration = null) - { - if (instanceMaterial == null || activeSettings == null) - { - return null; - } - - blinkTween?.Kill(); - - float closeDuration = activeSettings.blinkCloseDuration; - float openDuration = activeSettings.blinkOpenDuration; - if (totalDuration.HasValue) - { - float total = Mathf.Max(0.05f, totalDuration.Value); - closeDuration = total * 0.42f; - openDuration = total * 0.58f; - } - - blinkTween = DOTween.Sequence() - .Append(CreateBlinkTween(blinkOpen, 0f, closeDuration, Ease.InQuad)) - .Append(CreateBlinkTween(0f, 1f, openDuration, Ease.OutQuad)) - .OnComplete(() => SetBlinkOpen(1f)); - - return blinkTween; - } - - private void ShowOverlay(EyeVisualSettings settings) - { - if (settings == null || !settings.enableViewportOverlay) - { - ExitModule(); - return; - } - - PrepareHierarchyIfNeeded(); - activeSettings = settings; - ApplySettings(settings); - if (!editorPreviewActive) - { - SetBlinkOpen(1f); - } - - SetOverlayActive(true); - - if (settings.autoBlink && Application.isPlaying) - { - StartAutoBlink(settings); - } - } - - private void PushViewportParams(EyeVisualSettings settings) - { - instanceMaterial.SetFloat(EyeWidthId, settings.eyeWidth); - instanceMaterial.SetFloat(EyeHeightId, settings.eyeHeight); - instanceMaterial.SetFloat(EdgeSoftnessId, settings.edgeSoftness); - } - - private Tween CreateBlinkTween(float from, float to, float duration, Ease ease) - { - blinkOpen = from; - SetBlinkOpen(from); - return DOTween.To(() => blinkOpen, value => - { - blinkOpen = value; - SetBlinkOpen(value); - }, to, duration).SetEase(ease); - } - - private void StartAutoBlink(EyeVisualSettings settings) - { - StopAutoBlink(); - if (!settings.autoBlink) - { - return; - } - - autoBlinkRoutine = StartCoroutine(AutoBlinkRoutine(settings)); - } - - private IEnumerator AutoBlinkRoutine(EyeVisualSettings settings) - { - while (enabled && overlayCanvas != null && overlayCanvas.gameObject.activeInHierarchy) - { - float min = Mathf.Max(1f, settings.autoBlinkInterval.x); - float max = Mathf.Max(min, settings.autoBlinkInterval.y); - yield return new WaitForSeconds(Random.Range(min, max)); - - Tween blink = PlayBlink(); - if (blink != null) - { - yield return blink.WaitForCompletion(); - } - } - } - - private void StopEffects() - { - blinkTween?.Kill(); - blinkTween = null; - StopAutoBlink(); - } - - private void StopAutoBlink() - { - if (autoBlinkRoutine == null) - { - return; - } - - StopCoroutine(autoBlinkRoutine); - autoBlinkRoutine = null; - } - - public void SetBlinkOpenPreview(float value) - { - SetBlinkOpen(value); - } - - public float GetBlinkOpenPreview() => blinkOpen; - - private void SetBlinkOpen(float value) - { - blinkOpen = Mathf.Clamp01(value); - instanceMaterial?.SetFloat(BlinkOpenId, blinkOpen); - } - - private void SetOverlayActive(bool active) - { - if (overlayCanvas != null) - { - overlayCanvas.gameObject.SetActive(active); - } - } - - private void PrepareHierarchyIfNeeded() - { - if (hierarchyReady && overlayCanvas != null) - { - return; - } - - if (Application.isPlaying && !lifecycleReady) - { - ScheduleDeferredPrepare(); - return; - } - - EnsureOverlayHierarchy(); - hierarchyReady = overlayCanvas != null; - } - - private void ScheduleDeferredPrepare() - { - if (deferPrepareRoutine != null || !isActiveAndEnabled) - { - return; - } - - deferPrepareRoutine = StartCoroutine(DeferredPrepareHierarchy()); - } - - private IEnumerator DeferredPrepareHierarchy() - { - yield return null; - deferPrepareRoutine = null; - EnsureOverlayHierarchy(); - hierarchyReady = overlayCanvas != null; - - if (activeSettings != null && instanceMaterial != null) - { - PushViewportParams(activeSettings); - SetBlinkOpen(blinkOpen); - } - } - - private void EnsureOverlayHierarchy() - { - if (overlayCanvas == null) - { - var canvasGo = new GameObject("EyeViewportOverlayCanvas"); - canvasGo.transform.SetParent(transform, false); - - overlayCanvas = canvasGo.AddComponent(); - overlayCanvas.pixelPerfect = false; - - var scaler = canvasGo.AddComponent(); - scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; - scaler.referenceResolution = new Vector2(1920f, 1080f); - scaler.matchWidthOrHeight = 0.5f; - - canvasGo.AddComponent(); - - var imageGo = new GameObject("Overlay", typeof(RectTransform)); - imageGo.transform.SetParent(canvasGo.transform, false); - - var rect = (RectTransform)imageGo.transform; - rect.anchorMin = Vector2.zero; - rect.anchorMax = Vector2.one; - rect.offsetMin = Vector2.zero; - rect.offsetMax = Vector2.zero; - - overlayImage = imageGo.AddComponent(); - overlayImage.raycastTarget = false; - overlayImage.color = Color.black; - SetupMaterial(); - } - - ApplyCanvasRenderSettings(); - } - - private void SetupMaterial() - { - Shader shader = Shader.Find("UI/EyeViewportOverlay"); - if (shader == null) - { - Debug.LogError("[EyeViewportOverlay] 找不到 UI/EyeViewportOverlay shader。", this); - return; - } - - instanceMaterial = new Material(shader) - { - name = "EyeViewportOverlay (Instance)", - hideFlags = HideFlags.DontSave - }; - overlayImage.material = instanceMaterial; - } - - private void ApplyCanvasRenderSettings() - { - if (overlayCanvas == null) - { - return; - } - - overlayCanvas.renderMode = RenderMode.ScreenSpaceCamera; - overlayCanvas.worldCamera = ResolveUICamera(); - overlayCanvas.planeDistance = OverlayPlaneDistance; - overlayCanvas.sortingLayerName = OverlaySortingLayer; - overlayCanvas.sortingOrder = sortingOrder; - } - - private static Camera ResolveUICamera() - { - if (UIManager.Instance != null && UIManager.Instance.UICamera != null) - { - return UIManager.Instance.UICamera; - } - - var uiManager = FindObjectOfType(); - if (uiManager != null && uiManager.UICamera != null) - { - return uiManager.UICamera; - } - - return Camera.main; - } - } -} +using System.Collections; +using DG.Tweening; +using UnityEngine; +using UnityEngine.UI; + +namespace AibisDream +{ + /// + /// Mobile-friendly fullscreen eye viewport: elliptical sight + vertical ellipse blink. + /// + [DisallowMultipleComponent] + [ExecuteAlways] + public class EyeViewportOverlay : MonoBehaviour + { + private const string OverlaySortingLayer = "StartUI"; + private const float OverlayPlaneDistance = 10f; + + private static readonly int EyeWidthId = Shader.PropertyToID("_EyeWidth"); + private static readonly int EyeHeightId = Shader.PropertyToID("_EyeHeight"); + private static readonly int EdgeSoftnessId = Shader.PropertyToID("_EdgeSoftness"); + private static readonly int BlinkOpenId = Shader.PropertyToID("_BlinkOpen"); + + /// Persistence:本遮罩=0,UI Canvas=1,Dialog Canvas=3。 + [SerializeField] private int sortingOrder; + + private Canvas overlayCanvas; + private Image overlayImage; + private Material instanceMaterial; + private EyeVisualSettings activeSettings; + private Coroutine autoBlinkRoutine; + private Tween blinkTween; + private float blinkOpen = 1f; + private bool moduleActive; + private bool editorPreviewActive; + private bool hierarchyReady; + private bool lifecycleReady; + private Coroutine deferPrepareRoutine; + + private void Start() + { + lifecycleReady = true; + if (!Application.isPlaying) + { + return; + } + + PrepareHierarchyIfNeeded(); + if (!moduleActive && !editorPreviewActive) + { + SetOverlayActive(false); + } + } + + private void OnDestroy() + { + StopEffects(); + if (instanceMaterial != null) + { + Destroy(instanceMaterial); + } + } + + public void EnterModule(EyeVisualSettings settings) + { + moduleActive = true; + editorPreviewActive = false; + ShowOverlay(settings); + } + + public void ExitModule() + { + moduleActive = false; + if (editorPreviewActive) + { + return; + } + + StopEffects(); + SetOverlayActive(false); + activeSettings = null; + } + + public void EnterEditorPreview(EyeVisualSettings settings) + { + editorPreviewActive = true; + ShowOverlay(settings); + } + + public void ExitEditorPreview() + { + editorPreviewActive = false; + if (moduleActive) + { + ShowOverlay(activeSettings); + return; + } + + StopEffects(); + SetOverlayActive(false); + activeSettings = null; + } + + public void ApplySettings(EyeVisualSettings settings) + { + if (settings == null) + { + return; + } + + if (!settings.enableViewportOverlay) + { + if (editorPreviewActive) + { + ExitEditorPreview(); + } + else if (moduleActive) + { + ExitModule(); + } + + return; + } + + PrepareHierarchyIfNeeded(); + if (instanceMaterial == null) + { + return; + } + + activeSettings = settings; + PushViewportParams(settings); + SetBlinkOpen(blinkOpen); + + if (!overlayCanvas.gameObject.activeInHierarchy) + { + return; + } + + if (settings.autoBlink && Application.isPlaying && (moduleActive || editorPreviewActive)) + { + StartAutoBlink(settings); + } + else + { + StopAutoBlink(); + } + } + + public Tween PlayBlink(float? totalDuration = null) + { + if (instanceMaterial == null || activeSettings == null) + { + return null; + } + + blinkTween?.Kill(); + + float closeDuration = activeSettings.blinkCloseDuration; + float openDuration = activeSettings.blinkOpenDuration; + if (totalDuration.HasValue) + { + float total = Mathf.Max(0.05f, totalDuration.Value); + closeDuration = total * 0.42f; + openDuration = total * 0.58f; + } + + blinkTween = DOTween.Sequence() + .Append(CreateBlinkTween(blinkOpen, 0f, closeDuration, Ease.InQuad)) + .Append(CreateBlinkTween(0f, 1f, openDuration, Ease.OutQuad)) + .OnComplete(() => SetBlinkOpen(1f)); + + return blinkTween; + } + + private void ShowOverlay(EyeVisualSettings settings) + { + if (settings == null || !settings.enableViewportOverlay) + { + ExitModule(); + return; + } + + PrepareHierarchyIfNeeded(); + ApplyCanvasRenderSettings(); + activeSettings = settings; + ApplySettings(settings); + if (!editorPreviewActive) + { + SetBlinkOpen(1f); + } + + SetOverlayActive(true); + + if (settings.autoBlink && Application.isPlaying) + { + StartAutoBlink(settings); + } + } + + private void PushViewportParams(EyeVisualSettings settings) + { + instanceMaterial.SetFloat(EyeWidthId, settings.eyeWidth); + instanceMaterial.SetFloat(EyeHeightId, settings.eyeHeight); + instanceMaterial.SetFloat(EdgeSoftnessId, settings.edgeSoftness); + } + + private Tween CreateBlinkTween(float from, float to, float duration, Ease ease) + { + blinkOpen = from; + SetBlinkOpen(from); + return DOTween.To(() => blinkOpen, value => + { + blinkOpen = value; + SetBlinkOpen(value); + }, to, duration).SetEase(ease); + } + + private void StartAutoBlink(EyeVisualSettings settings) + { + StopAutoBlink(); + if (!settings.autoBlink) + { + return; + } + + autoBlinkRoutine = StartCoroutine(AutoBlinkRoutine(settings)); + } + + private IEnumerator AutoBlinkRoutine(EyeVisualSettings settings) + { + while (enabled && overlayCanvas != null && overlayCanvas.gameObject.activeInHierarchy) + { + float min = Mathf.Max(1f, settings.autoBlinkInterval.x); + float max = Mathf.Max(min, settings.autoBlinkInterval.y); + yield return new WaitForSeconds(Random.Range(min, max)); + + Tween blink = PlayBlink(); + if (blink != null) + { + yield return blink.WaitForCompletion(); + } + } + } + + private void StopEffects() + { + blinkTween?.Kill(); + blinkTween = null; + StopAutoBlink(); + } + + private void StopAutoBlink() + { + if (autoBlinkRoutine == null) + { + return; + } + + StopCoroutine(autoBlinkRoutine); + autoBlinkRoutine = null; + } + + public void SetBlinkOpenPreview(float value) + { + SetBlinkOpen(value); + } + + public float GetBlinkOpenPreview() => blinkOpen; + + private void SetBlinkOpen(float value) + { + blinkOpen = Mathf.Clamp01(value); + instanceMaterial?.SetFloat(BlinkOpenId, blinkOpen); + } + + private void SetOverlayActive(bool active) + { + if (overlayCanvas != null) + { + overlayCanvas.gameObject.SetActive(active); + } + } + + private void PrepareHierarchyIfNeeded() + { + if (Application.isPlaying && !lifecycleReady) + { + ScheduleDeferredPrepare(); + return; + } + + EnsureOverlayHierarchy(); + hierarchyReady = overlayCanvas != null; + ApplyCanvasRenderSettings(); + } + + private void ScheduleDeferredPrepare() + { + if (deferPrepareRoutine != null || !isActiveAndEnabled) + { + return; + } + + deferPrepareRoutine = StartCoroutine(DeferredPrepareHierarchy()); + } + + private IEnumerator DeferredPrepareHierarchy() + { + yield return null; + deferPrepareRoutine = null; + EnsureOverlayHierarchy(); + hierarchyReady = overlayCanvas != null; + + if (activeSettings != null && instanceMaterial != null) + { + PushViewportParams(activeSettings); + SetBlinkOpen(blinkOpen); + } + + ApplyCanvasRenderSettings(); + } + + private void EnsureOverlayHierarchy() + { + if (overlayCanvas == null) + { + var canvasGo = new GameObject("EyeViewportOverlayCanvas"); + canvasGo.transform.SetParent(transform, false); + + overlayCanvas = canvasGo.AddComponent(); + overlayCanvas.pixelPerfect = false; + + var scaler = canvasGo.AddComponent(); + scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; + scaler.referenceResolution = new Vector2(1920f, 1080f); + scaler.matchWidthOrHeight = 0.5f; + + canvasGo.AddComponent(); + + var imageGo = new GameObject("Overlay", typeof(RectTransform)); + imageGo.transform.SetParent(canvasGo.transform, false); + + var rect = (RectTransform)imageGo.transform; + rect.anchorMin = Vector2.zero; + rect.anchorMax = Vector2.one; + rect.offsetMin = Vector2.zero; + rect.offsetMax = Vector2.zero; + + overlayImage = imageGo.AddComponent(); + overlayImage.raycastTarget = false; + overlayImage.color = Color.black; + SetupMaterial(); + } + + ApplyCanvasRenderSettings(); + } + + private void SetupMaterial() + { + Shader shader = Shader.Find("UI/EyeViewportOverlay"); + if (shader == null) + { + Debug.LogError("[EyeViewportOverlay] 找不到 UI/EyeViewportOverlay shader。", this); + return; + } + + instanceMaterial = new Material(shader) + { + name = "EyeViewportOverlay (Instance)", + hideFlags = HideFlags.DontSave + }; + overlayImage.material = instanceMaterial; + } + + private void ApplyCanvasRenderSettings() + { + if (overlayCanvas == null) + { + return; + } + + overlayCanvas.renderMode = RenderMode.ScreenSpaceCamera; + overlayCanvas.worldCamera = ResolveUICamera(); + overlayCanvas.planeDistance = OverlayPlaneDistance; + overlayCanvas.sortingLayerName = OverlaySortingLayer; + overlayCanvas.sortingOrder = sortingOrder; + SetOverlayUILayer(overlayCanvas.gameObject); + } + + private static void SetOverlayUILayer(GameObject root) + { + int uiLayer = LayerMask.NameToLayer("UI"); + if (uiLayer < 0) + { + uiLayer = 5; + } + + foreach (Transform node in root.GetComponentsInChildren(true)) + { + node.gameObject.layer = uiLayer; + } + } + + private static Camera ResolveUICamera() + { + if (UIManager.Instance != null && UIManager.Instance.UICamera != null) + { + return UIManager.Instance.UICamera; + } + + var uiManager = Object.FindObjectOfType(); + if (uiManager != null && uiManager.UICamera != null) + { + return uiManager.UICamera; + } + + return Camera.main; + } + } +} + \ No newline at end of file