using System; using System.Collections; using AdaptUI; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; namespace AppUI { /// /// Entry / Login 竖屏,进 Home 横屏。真机用 Screen.orientation;编辑器用 Game 视图 1170×2532 ↔ 2532×1170。 /// public static class ScreenOrientationHelper { public const int PortraitWidth = 1170; public const int PortraitHeight = 2532; public const int LandscapeWidth = 2532; public const int LandscapeHeight = 1170; const float BlackFadeInDuration = 0.2f; const float BlackFadeOutDuration = 0.45f; const float BlackHoldBeforeFadeOut = 0.08f; static Canvas _blackCanvas; static CanvasGroup _blackCanvasGroup; static Coroutine _transitionCoroutine; public static void ApplyPortraitForBoot() { Screen.orientation = ScreenOrientation.Portrait; Screen.autorotateToLandscapeRight = false; Screen.autorotateToLandscapeLeft = false; Screen.autorotateToPortrait = false; Screen.autorotateToPortraitUpsideDown = false; } public static IEnumerator SwitchToPortraitEditorAndWait(float maxWaitSeconds = 2f) { #if UNITY_EDITOR if (!ScreenOrientationEditorSettings.IsEnabled) yield break; yield return GameViewSizeHelper.EditorApplyPortraitCoroutine(maxWaitSeconds); #else yield break; #endif } public static IEnumerator SwitchToLandscapeAndWait(float maxWaitSeconds = 2f) { yield return FadeBlackOverlay(1f, BlackFadeInDuration); Screen.autorotateToLandscapeRight = true; Screen.autorotateToLandscapeLeft = true; Screen.autorotateToPortrait = false; Screen.autorotateToPortraitUpsideDown = false; Screen.orientation = ScreenOrientation.LandscapeLeft; #if UNITY_EDITOR if (ScreenOrientationEditorSettings.IsEnabled) yield return GameViewSizeHelper.EditorSwapOrientationCoroutine(true, maxWaitSeconds); #else float elapsed = 0f; while (elapsed < maxWaitSeconds) { if (Screen.width >= Screen.height) break; elapsed += Time.unscaledDeltaTime; yield return null; } yield return null; #endif DeviceTypeHelper.InvalidateDeviceTypeCache(); } public static IEnumerator FadeOutBlackOverlay(float duration = BlackFadeOutDuration) { EnsureBlackOverlay(); if (_blackCanvasGroup == null) yield break; _blackCanvas.gameObject.SetActive(true); if (_blackCanvasGroup.alpha < 0.01f) yield break; yield return FadeBlackOverlay(0f, duration); } /// /// 竖屏切横屏 → 黑屏 → 加载场景 → 淡出。协程跑在 DontDestroyOnLoad 上,避免 LoadScene 销毁原场景后无法淡出。 /// public static void SwitchToLandscapeAndLoadScene(string sceneName) { var host = CoroutineRunnerHost.Instance; if (_transitionCoroutine != null) host.StopCoroutine(_transitionCoroutine); _transitionCoroutine = host.StartCoroutine(SwitchToLandscapeAndLoadSceneCoroutine(sceneName)); } /// /// 异步激活场景后淡出黑屏(Entry 等场景会在激活后被卸载,须提前在 DontDestroyOnLoad 上调度)。 /// public static void ScheduleFadeOutBlackOverlayAfterSceneLoad(string expectedSceneName) { var host = CoroutineRunnerHost.Instance; if (_transitionCoroutine != null) host.StopCoroutine(_transitionCoroutine); _transitionCoroutine = host.StartCoroutine(FadeOutAfterSceneLoadCoroutine(expectedSceneName)); } static IEnumerator SwitchToLandscapeAndLoadSceneCoroutine(string sceneName) { yield return SwitchToLandscapeAndWait(); SceneManager.LoadScene(sceneName, LoadSceneMode.Single); yield return WaitSceneReadyForFadeOut(sceneName); yield return FadeOutBlackOverlay(); _transitionCoroutine = null; } static IEnumerator FadeOutAfterSceneLoadCoroutine(string expectedSceneName) { yield return WaitSceneReadyForFadeOut(expectedSceneName); yield return FadeOutBlackOverlay(); _transitionCoroutine = null; } static IEnumerator WaitSceneReadyForFadeOut(string expectedSceneName) { while (SceneManager.GetActiveScene().name != expectedSceneName) yield return null; float holdUntil = Time.unscaledTime + BlackHoldBeforeFadeOut; while (Time.unscaledTime < holdUntil) yield return null; yield return new WaitForEndOfFrame(); yield return new WaitForEndOfFrame(); EnsureBlackOverlay(); _blackCanvas.gameObject.SetActive(true); _blackCanvasGroup.alpha = 1f; _blackCanvasGroup.blocksRaycasts = true; } static void EnsureBlackOverlay() { if (_blackCanvas != null) return; var go = new GameObject("[OrientationBlackOverlay]"); UnityEngine.Object.DontDestroyOnLoad(go); _blackCanvas = go.AddComponent(); _blackCanvas.renderMode = RenderMode.ScreenSpaceOverlay; _blackCanvas.sortingOrder = short.MaxValue; var scaler = go.AddComponent(); scaler.uiScaleMode = CanvasScaler.ScaleMode.ConstantPixelSize; go.AddComponent(); var imageGo = new GameObject("Black"); imageGo.transform.SetParent(go.transform, false); var rect = imageGo.AddComponent(); rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = Vector2.zero; rect.offsetMax = Vector2.zero; var image = imageGo.AddComponent(); image.color = Color.black; image.raycastTarget = true; _blackCanvasGroup = go.AddComponent(); _blackCanvasGroup.alpha = 0f; _blackCanvasGroup.blocksRaycasts = true; go.SetActive(false); } static IEnumerator FadeBlackOverlay(float targetAlpha, float duration) { EnsureBlackOverlay(); _blackCanvas.gameObject.SetActive(true); _blackCanvasGroup.blocksRaycasts = targetAlpha > 0f; float startAlpha = _blackCanvasGroup.alpha; if (duration <= 0f) { _blackCanvasGroup.alpha = targetAlpha; } else { float startTime = Time.unscaledTime; float endTime = startTime + duration; while (Time.unscaledTime < endTime) { float t = Mathf.InverseLerp(startTime, endTime, Time.unscaledTime); t = Mathf.SmoothStep(0f, 1f, t); _blackCanvasGroup.alpha = Mathf.Lerp(startAlpha, targetAlpha, t); yield return null; } _blackCanvasGroup.alpha = targetAlpha; } if (targetAlpha <= 0f) _blackCanvas.gameObject.SetActive(false); } public static void RunSwitchToLandscapeThen(Action onReady) { CoroutineRunnerHost.Instance.StartCoroutine(RunSwitchCoroutine(onReady)); } static IEnumerator RunSwitchCoroutine(Action onReady) { yield return SwitchToLandscapeAndWait(); onReady?.Invoke(); } sealed class CoroutineRunnerHost : MonoBehaviour { static CoroutineRunnerHost _instance; public static CoroutineRunnerHost Instance { get { if (_instance != null) return _instance; var go = new GameObject("[ScreenOrientationHelper]"); DontDestroyOnLoad(go); _instance = go.AddComponent(); return _instance; } } } } }