ScreenOrientationHelper.cs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. using System;
  2. using System.Collections;
  3. using AdaptUI;
  4. using UnityEngine;
  5. using UnityEngine.SceneManagement;
  6. using UnityEngine.UI;
  7. namespace AppUI
  8. {
  9. /// <summary>
  10. /// Entry / Login 竖屏,进 Home 横屏。真机用 Screen.orientation;编辑器用 Game 视图 1170×2532 ↔ 2532×1170。
  11. /// </summary>
  12. public static class ScreenOrientationHelper
  13. {
  14. public const int PortraitWidth = 1170;
  15. public const int PortraitHeight = 2532;
  16. public const int LandscapeWidth = 2532;
  17. public const int LandscapeHeight = 1170;
  18. const float BlackFadeInDuration = 0.2f;
  19. const float BlackFadeOutDuration = 0.45f;
  20. const float BlackHoldBeforeFadeOut = 0.08f;
  21. static Canvas _blackCanvas;
  22. static CanvasGroup _blackCanvasGroup;
  23. static Coroutine _transitionCoroutine;
  24. public static void ApplyPortraitForBoot()
  25. {
  26. Screen.orientation = ScreenOrientation.Portrait;
  27. Screen.autorotateToLandscapeRight = false;
  28. Screen.autorotateToLandscapeLeft = false;
  29. Screen.autorotateToPortrait = false;
  30. Screen.autorotateToPortraitUpsideDown = false;
  31. }
  32. public static IEnumerator SwitchToPortraitEditorAndWait(float maxWaitSeconds = 2f)
  33. {
  34. #if UNITY_EDITOR
  35. if (!ScreenOrientationEditorSettings.IsEnabled)
  36. yield break;
  37. yield return GameViewSizeHelper.EditorApplyPortraitCoroutine(maxWaitSeconds);
  38. #else
  39. yield break;
  40. #endif
  41. }
  42. public static IEnumerator SwitchToLandscapeAndWait(float maxWaitSeconds = 2f)
  43. {
  44. yield return FadeBlackOverlay(1f, BlackFadeInDuration);
  45. Screen.autorotateToLandscapeRight = true;
  46. Screen.autorotateToLandscapeLeft = true;
  47. Screen.autorotateToPortrait = false;
  48. Screen.autorotateToPortraitUpsideDown = false;
  49. Screen.orientation = ScreenOrientation.LandscapeLeft;
  50. #if UNITY_EDITOR
  51. if (ScreenOrientationEditorSettings.IsEnabled)
  52. yield return GameViewSizeHelper.EditorSwapOrientationCoroutine(true, maxWaitSeconds);
  53. #else
  54. float elapsed = 0f;
  55. while (elapsed < maxWaitSeconds)
  56. {
  57. if (Screen.width >= Screen.height)
  58. break;
  59. elapsed += Time.unscaledDeltaTime;
  60. yield return null;
  61. }
  62. yield return null;
  63. #endif
  64. DeviceTypeHelper.InvalidateDeviceTypeCache();
  65. }
  66. public static IEnumerator FadeOutBlackOverlay(float duration = BlackFadeOutDuration)
  67. {
  68. EnsureBlackOverlay();
  69. if (_blackCanvasGroup == null)
  70. yield break;
  71. _blackCanvas.gameObject.SetActive(true);
  72. if (_blackCanvasGroup.alpha < 0.01f)
  73. yield break;
  74. yield return FadeBlackOverlay(0f, duration);
  75. }
  76. /// <summary>
  77. /// 竖屏切横屏 → 黑屏 → 加载场景 → 淡出。协程跑在 DontDestroyOnLoad 上,避免 LoadScene 销毁原场景后无法淡出。
  78. /// </summary>
  79. public static void SwitchToLandscapeAndLoadScene(string sceneName)
  80. {
  81. var host = CoroutineRunnerHost.Instance;
  82. if (_transitionCoroutine != null)
  83. host.StopCoroutine(_transitionCoroutine);
  84. _transitionCoroutine = host.StartCoroutine(SwitchToLandscapeAndLoadSceneCoroutine(sceneName));
  85. }
  86. /// <summary>
  87. /// 异步激活场景后淡出黑屏(Entry 等场景会在激活后被卸载,须提前在 DontDestroyOnLoad 上调度)。
  88. /// </summary>
  89. public static void ScheduleFadeOutBlackOverlayAfterSceneLoad(string expectedSceneName)
  90. {
  91. var host = CoroutineRunnerHost.Instance;
  92. if (_transitionCoroutine != null)
  93. host.StopCoroutine(_transitionCoroutine);
  94. _transitionCoroutine = host.StartCoroutine(FadeOutAfterSceneLoadCoroutine(expectedSceneName));
  95. }
  96. static IEnumerator SwitchToLandscapeAndLoadSceneCoroutine(string sceneName)
  97. {
  98. yield return SwitchToLandscapeAndWait();
  99. SceneManager.LoadScene(sceneName, LoadSceneMode.Single);
  100. yield return WaitSceneReadyForFadeOut(sceneName);
  101. yield return FadeOutBlackOverlay();
  102. _transitionCoroutine = null;
  103. }
  104. static IEnumerator FadeOutAfterSceneLoadCoroutine(string expectedSceneName)
  105. {
  106. yield return WaitSceneReadyForFadeOut(expectedSceneName);
  107. yield return FadeOutBlackOverlay();
  108. _transitionCoroutine = null;
  109. }
  110. static IEnumerator WaitSceneReadyForFadeOut(string expectedSceneName)
  111. {
  112. while (SceneManager.GetActiveScene().name != expectedSceneName)
  113. yield return null;
  114. float holdUntil = Time.unscaledTime + BlackHoldBeforeFadeOut;
  115. while (Time.unscaledTime < holdUntil)
  116. yield return null;
  117. yield return new WaitForEndOfFrame();
  118. yield return new WaitForEndOfFrame();
  119. EnsureBlackOverlay();
  120. _blackCanvas.gameObject.SetActive(true);
  121. _blackCanvasGroup.alpha = 1f;
  122. _blackCanvasGroup.blocksRaycasts = true;
  123. }
  124. static void EnsureBlackOverlay()
  125. {
  126. if (_blackCanvas != null)
  127. return;
  128. var go = new GameObject("[OrientationBlackOverlay]");
  129. UnityEngine.Object.DontDestroyOnLoad(go);
  130. _blackCanvas = go.AddComponent<Canvas>();
  131. _blackCanvas.renderMode = RenderMode.ScreenSpaceOverlay;
  132. _blackCanvas.sortingOrder = short.MaxValue;
  133. var scaler = go.AddComponent<CanvasScaler>();
  134. scaler.uiScaleMode = CanvasScaler.ScaleMode.ConstantPixelSize;
  135. go.AddComponent<GraphicRaycaster>();
  136. var imageGo = new GameObject("Black");
  137. imageGo.transform.SetParent(go.transform, false);
  138. var rect = imageGo.AddComponent<RectTransform>();
  139. rect.anchorMin = Vector2.zero;
  140. rect.anchorMax = Vector2.one;
  141. rect.offsetMin = Vector2.zero;
  142. rect.offsetMax = Vector2.zero;
  143. var image = imageGo.AddComponent<Image>();
  144. image.color = Color.black;
  145. image.raycastTarget = true;
  146. _blackCanvasGroup = go.AddComponent<CanvasGroup>();
  147. _blackCanvasGroup.alpha = 0f;
  148. _blackCanvasGroup.blocksRaycasts = true;
  149. go.SetActive(false);
  150. }
  151. static IEnumerator FadeBlackOverlay(float targetAlpha, float duration)
  152. {
  153. EnsureBlackOverlay();
  154. _blackCanvas.gameObject.SetActive(true);
  155. _blackCanvasGroup.blocksRaycasts = targetAlpha > 0f;
  156. float startAlpha = _blackCanvasGroup.alpha;
  157. if (duration <= 0f)
  158. {
  159. _blackCanvasGroup.alpha = targetAlpha;
  160. }
  161. else
  162. {
  163. float startTime = Time.unscaledTime;
  164. float endTime = startTime + duration;
  165. while (Time.unscaledTime < endTime)
  166. {
  167. float t = Mathf.InverseLerp(startTime, endTime, Time.unscaledTime);
  168. t = Mathf.SmoothStep(0f, 1f, t);
  169. _blackCanvasGroup.alpha = Mathf.Lerp(startAlpha, targetAlpha, t);
  170. yield return null;
  171. }
  172. _blackCanvasGroup.alpha = targetAlpha;
  173. }
  174. if (targetAlpha <= 0f)
  175. _blackCanvas.gameObject.SetActive(false);
  176. }
  177. public static void RunSwitchToLandscapeThen(Action onReady)
  178. {
  179. CoroutineRunnerHost.Instance.StartCoroutine(RunSwitchCoroutine(onReady));
  180. }
  181. static IEnumerator RunSwitchCoroutine(Action onReady)
  182. {
  183. yield return SwitchToLandscapeAndWait();
  184. onReady?.Invoke();
  185. }
  186. sealed class CoroutineRunnerHost : MonoBehaviour
  187. {
  188. static CoroutineRunnerHost _instance;
  189. public static CoroutineRunnerHost Instance
  190. {
  191. get
  192. {
  193. if (_instance != null)
  194. return _instance;
  195. var go = new GameObject("[ScreenOrientationHelper]");
  196. DontDestroyOnLoad(go);
  197. _instance = go.AddComponent<CoroutineRunnerHost>();
  198. return _instance;
  199. }
  200. }
  201. }
  202. }
  203. }