ResolutionMonitor.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEngine;
  5. using UnityEngine.Serialization;
  6. #pragma warning disable 0618 // disable "never assigned" warnings
  7. namespace TheraBytes.BetterUi
  8. {
  9. [HelpURL("https://documentation.therabytes.de/better-ui/ResolutionMonitor.html")]
  10. public class ResolutionMonitor : SingletonScriptableObject<ResolutionMonitor>
  11. {
  12. static string FilePath { get { return "TheraBytes/Resources/ResolutionMonitor"; } }
  13. #region Obsolete
  14. [Obsolete("Use 'GetOptimizedResolution()' instead.")]
  15. public static Vector2 OptimizedResolution
  16. {
  17. get { return ResolutionMonitor.Instance.optimizedResolutionFallback; }
  18. set
  19. {
  20. if (ResolutionMonitor.Instance.optimizedResolutionFallback == value)
  21. return;
  22. ResolutionMonitor.Instance.optimizedResolutionFallback = value;
  23. CallResolutionChanged();
  24. }
  25. }
  26. [Obsolete("Use 'GetOptimizedDpi()' instead.")]
  27. public static float OptimizedDpi
  28. {
  29. get { return ResolutionMonitor.Instance.optimizedDpiFallback; }
  30. set
  31. {
  32. if (ResolutionMonitor.Instance.optimizedDpiFallback == value)
  33. return;
  34. ResolutionMonitor.Instance.optimizedDpiFallback = value;
  35. CallResolutionChanged();
  36. }
  37. }
  38. #endregion
  39. public static Vector2 CurrentResolution
  40. {
  41. get
  42. {
  43. if (lastScreenResolution == Vector2.zero)
  44. {
  45. lastScreenResolution = new Vector2(Screen.width, Screen.height);
  46. }
  47. return lastScreenResolution;
  48. }
  49. }
  50. public static float CurrentDpi
  51. {
  52. get
  53. {
  54. if (lastDpi == 0)
  55. {
  56. lastDpi = Instance.dpiManager.GetDpi();
  57. }
  58. return lastDpi;
  59. }
  60. }
  61. public string FallbackName { get { return fallbackName; } set { fallbackName = value; } }
  62. public static Vector2 OptimizedResolutionFallback { get { return ResolutionMonitor.Instance.optimizedResolutionFallback; } }
  63. public static float OptimizedDpiFallback { get { return ResolutionMonitor.Instance.optimizedDpiFallback; } }
  64. [FormerlySerializedAs("optimizedResolution")]
  65. [SerializeField]
  66. Vector2 optimizedResolutionFallback = new Vector2(1080, 1920);
  67. [FormerlySerializedAs("optimizedDpi")]
  68. [SerializeField]
  69. float optimizedDpiFallback = 96;
  70. [SerializeField]
  71. string fallbackName = "Portrait";
  72. [SerializeField]
  73. StaticSizerMethod[] staticSizerMethods = new StaticSizerMethod[5];
  74. [SerializeField]
  75. DpiManager dpiManager = new DpiManager();
  76. ScreenTypeConditions currentScreenConfig;
  77. [SerializeField]
  78. List<ScreenTypeConditions> optimizedScreens = new List<ScreenTypeConditions>()
  79. {
  80. new ScreenTypeConditions("Landscape", typeof(IsCertainScreenOrientation)),
  81. };
  82. public List<ScreenTypeConditions> OptimizedScreens { get { return optimizedScreens; } }
  83. static Dictionary<string, ScreenTypeConditions> lookUpScreens = new Dictionary<string, ScreenTypeConditions>();
  84. #region Screen Tags
  85. static HashSet<string> screenTags = new HashSet<string>();
  86. public static IEnumerable<string> CurrentScreenTags { get { return screenTags; } }
  87. public static bool AddScreenTag(string screenTag)
  88. {
  89. if(screenTags.Add(screenTag))
  90. {
  91. isDirty = true;
  92. Update();
  93. return true;
  94. }
  95. return false;
  96. }
  97. public static bool RemoveScreenTag(string screenTag)
  98. {
  99. if (screenTags.Remove(screenTag))
  100. {
  101. isDirty = true;
  102. Update();
  103. return true;
  104. }
  105. return false;
  106. }
  107. public static void ClearScreenTags()
  108. {
  109. screenTags.Clear();
  110. isDirty = true;
  111. Update();
  112. }
  113. #endregion
  114. #if UNITY_EDITOR && UNITY_2018_3_OR_NEWER
  115. static UnityEditor.SceneManagement.StageHandle currentStage;
  116. #endif
  117. public static ScreenTypeConditions CurrentScreenConfiguration
  118. {
  119. get
  120. {
  121. #if UNITY_EDITOR
  122. if (simulatedScreenConfig != null)
  123. {
  124. return simulatedScreenConfig;
  125. }
  126. #endif
  127. return ResolutionMonitor.Instance.currentScreenConfig;
  128. }
  129. }
  130. public static ScreenTypeConditions GetConfig(string name)
  131. {
  132. if (lookUpScreens.Count == 0)
  133. {
  134. foreach (var config in ResolutionMonitor.Instance.optimizedScreens)
  135. {
  136. lookUpScreens.Add(config.Name, config);
  137. }
  138. }
  139. if (!(lookUpScreens.ContainsKey(name)))
  140. {
  141. var config = ResolutionMonitor.Instance.optimizedScreens.FirstOrDefault(o => o.Name == name);
  142. if (config != null)
  143. {
  144. lookUpScreens.Add(name, config);
  145. return config;
  146. }
  147. else
  148. {
  149. return null;
  150. }
  151. }
  152. return lookUpScreens[name];
  153. }
  154. public static ScreenInfo GetOpimizedScreenInfo(string name)
  155. {
  156. if (string.IsNullOrEmpty(name))
  157. {
  158. return new ScreenInfo(OptimizedResolutionFallback, OptimizedDpiFallback);
  159. }
  160. return GetConfig(name).OptimizedScreenInfo;
  161. }
  162. public static IEnumerable<ScreenTypeConditions> GetCurrentScreenConfigurations()
  163. {
  164. foreach (ScreenTypeConditions config in ResolutionMonitor.Instance.optimizedScreens)
  165. {
  166. if (config.IsActive)
  167. yield return config;
  168. }
  169. }
  170. static Vector2 lastScreenResolution;
  171. static float lastDpi;
  172. static bool isDirty;
  173. #if UNITY_EDITOR
  174. static Type gameViewType = null;
  175. static UnityEditor.EditorWindow gameViewWindow = null;
  176. static Version unityVersion;
  177. static ScreenTypeConditions simulatedScreenConfig;
  178. public static ScreenTypeConditions SimulatedScreenConfig
  179. {
  180. get { return simulatedScreenConfig; }
  181. set
  182. {
  183. if (simulatedScreenConfig != value)
  184. isDirty = true;
  185. simulatedScreenConfig = value;
  186. }
  187. }
  188. void OnEnable()
  189. {
  190. RegisterCallbacks();
  191. }
  192. void OnDisable()
  193. {
  194. UnregisterCallbacks();
  195. }
  196. static void RegisterCallbacks()
  197. {
  198. unityVersion = UnityEditorInternal.InternalEditorUtility.GetUnityVersion();
  199. isDirty = true;
  200. UnityEditor.EditorApplication.update += Update;
  201. #if UNITY_5_6_OR_NEWER
  202. UnityEditor.SceneManagement.EditorSceneManager.sceneOpened += SceneOpened;
  203. #endif
  204. #if UNITY_2018_0_OR_NEWER
  205. UnityEditor.EditorApplication.playModeStateChanged += PlayModeStateChanged;
  206. #else
  207. UnityEditor.EditorApplication.playmodeStateChanged += PlayModeStateChanged;
  208. #endif
  209. }
  210. static void UnregisterCallbacks()
  211. {
  212. UnityEditor.EditorApplication.update -= Update;
  213. #if UNITY_5_6_OR_NEWER
  214. UnityEditor.SceneManagement.EditorSceneManager.sceneOpened -= SceneOpened;
  215. #endif
  216. #if UNITY_2018_0_OR_NEWER
  217. UnityEditor.EditorApplication.playModeStateChanged -= PlayModeStateChanged;
  218. #else
  219. UnityEditor.EditorApplication.playmodeStateChanged -= PlayModeStateChanged;
  220. #endif
  221. }
  222. #if UNITY_5_6_OR_NEWER
  223. private static void SceneOpened(UnityEngine.SceneManagement.Scene scene, UnityEditor.SceneManagement.OpenSceneMode mode)
  224. {
  225. isDirty = true;
  226. Update();
  227. }
  228. #endif
  229. private static void PlayModeStateChanged()
  230. {
  231. if(!UnityEditor.EditorApplication.isPlaying)
  232. {
  233. ClearScreenTags();
  234. }
  235. Instance.ResolutionChanged();
  236. }
  237. #else
  238. void OnEnable()
  239. {
  240. ResolutionChanged();
  241. }
  242. #endif
  243. public static float InvokeStaticMethod(ImpactMode mode, Component caller, Vector2 optimizedResolution, Vector2 actualResolution, float optimizedDpi, float actualDpi)
  244. {
  245. int idx = 0;
  246. switch (mode)
  247. {
  248. case ImpactMode.StaticMethod1: idx = 0; break;
  249. case ImpactMode.StaticMethod2: idx = 1; break;
  250. case ImpactMode.StaticMethod3: idx = 2; break;
  251. case ImpactMode.StaticMethod4: idx = 3; break;
  252. case ImpactMode.StaticMethod5: idx = 4; break;
  253. default: throw new ArgumentException();
  254. }
  255. return (ResolutionMonitor.HasInstance && Instance.staticSizerMethods[idx] != null)
  256. ? Instance.staticSizerMethods[idx].Invoke(caller, optimizedResolution, actualResolution, optimizedDpi, actualDpi)
  257. : 1;
  258. }
  259. public static void MarkDirty()
  260. {
  261. ResolutionMonitor.isDirty = true;
  262. }
  263. public static float GetOptimizedDpi(string screenName)
  264. {
  265. if (string.IsNullOrEmpty(screenName) || screenName == Instance.fallbackName)
  266. return OptimizedDpiFallback;
  267. var s = Instance.optimizedScreens.FirstOrDefault(o => o.Name == screenName);
  268. if (s == null)
  269. {
  270. Debug.LogError("Screen Config with name " + screenName + " could not be found.");
  271. return OptimizedDpiFallback;
  272. }
  273. return s.OptimizedDpi;
  274. }
  275. public static Vector2 GetOptimizedResolution(string screenName)
  276. {
  277. if (string.IsNullOrEmpty(screenName) || screenName == Instance.fallbackName)
  278. return OptimizedResolutionFallback;
  279. var s = GetConfig(screenName);
  280. if (s == null)
  281. return OptimizedResolutionFallback;
  282. return s.OptimizedResolution;
  283. }
  284. public static bool IsOptimizedResolution(int width, int height)
  285. {
  286. if ((int)OptimizedResolutionFallback.x == width && (int)OptimizedResolutionFallback.y == height)
  287. return true;
  288. foreach (var config in Instance.optimizedScreens)
  289. {
  290. ScreenInfo si = config.OptimizedScreenInfo;
  291. if (si != null && (int)si.Resolution.x == width && (int)si.Resolution.y == height)
  292. return true;
  293. }
  294. return false;
  295. }
  296. public static void Update()
  297. {
  298. #if UNITY_EDITOR
  299. // check if file was deleted
  300. if(!HasInstance)
  301. {
  302. UnregisterCallbacks();
  303. return;
  304. }
  305. #endif
  306. isDirty = isDirty
  307. #if UNITY_EDITOR // should never change in reality...
  308. || (Instance.GetCurrentDpi() != lastDpi)
  309. #endif
  310. || GetCurrentResolution() != lastScreenResolution;
  311. #if UNITY_EDITOR && UNITY_2018_3_OR_NEWER
  312. if(!isDirty)
  313. {
  314. var stage = UnityEditor.SceneManagement.StageUtility.GetCurrentStageHandle();
  315. if (stage != currentStage)
  316. {
  317. currentStage = stage;
  318. isDirty = true;
  319. }
  320. }
  321. #endif
  322. if (isDirty)
  323. {
  324. CallResolutionChanged();
  325. isDirty = false;
  326. }
  327. }
  328. public static void CallResolutionChanged()
  329. {
  330. Instance.ResolutionChanged();
  331. }
  332. public void ResolutionChanged()
  333. {
  334. lastScreenResolution = GetCurrentResolution();
  335. lastDpi = GetCurrentDpi();
  336. currentScreenConfig = null;
  337. bool foundConfig = false;
  338. foreach (var config in optimizedScreens)
  339. {
  340. if (config.IsScreenType() && !(foundConfig))
  341. {
  342. currentScreenConfig = config;
  343. foundConfig = true;
  344. }
  345. }
  346. if (ResolutionMonitor.HasInstance) // preserve calling too early
  347. {
  348. foreach (IResolutionDependency rd in AllResolutionDependencies())
  349. {
  350. if (!(rd as Behaviour).isActiveAndEnabled)
  351. continue;
  352. rd.OnResolutionChanged();
  353. }
  354. }
  355. #if UNITY_EDITOR
  356. if (IsZoomPossible())
  357. {
  358. FindAndStoreGameView();
  359. if (gameViewWindow != null)
  360. {
  361. var method = gameViewType.GetMethod("UpdateZoomAreaAndParent",
  362. System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
  363. try
  364. {
  365. if (method != null)
  366. method.Invoke(gameViewWindow, null);
  367. }
  368. catch (Exception) { }
  369. }
  370. }
  371. #endif
  372. }
  373. static IEnumerable<IResolutionDependency> AllResolutionDependencies()
  374. {
  375. var allObjects = GetAllEditableObjects();
  376. // first update the "override screen properties", because other objects rely on them
  377. foreach (GameObject go in allObjects)
  378. {
  379. var resDeps = go.GetComponents<OverrideScreenProperties>();
  380. foreach (IResolutionDependency comp in resDeps)
  381. {
  382. yield return comp;
  383. }
  384. }
  385. // then update all other objects
  386. foreach (GameObject go in allObjects)
  387. {
  388. var resDeps = go.GetComponents<Behaviour>().OfType<IResolutionDependency>();
  389. foreach (IResolutionDependency comp in resDeps)
  390. {
  391. if (comp is OverrideScreenProperties)
  392. continue;
  393. yield return comp;
  394. }
  395. }
  396. }
  397. static IEnumerable<GameObject> GetAllEditableObjects()
  398. {
  399. var allObjects =
  400. #if UNITY_2022_2_OR_NEWER
  401. UnityEngine.Object.FindObjectsByType<GameObject>(FindObjectsInactive.Include, FindObjectsSortMode.None);
  402. #else
  403. UnityEngine.Object.FindObjectsOfType<GameObject>();
  404. #endif
  405. foreach (GameObject go in allObjects)
  406. yield return go;
  407. #if UNITY_EDITOR && UNITY_2018_3_OR_NEWER
  408. var prefabStage =
  409. #if UNITY_2021_2_OR_NEWER
  410. UnityEditor.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
  411. #else
  412. UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
  413. #endif
  414. if (prefabStage != null)
  415. {
  416. foreach(GameObject root in prefabStage.scene.GetRootGameObjects())
  417. {
  418. foreach (GameObject go in IterateHierarchy(root))
  419. {
  420. yield return go;
  421. }
  422. }
  423. }
  424. #endif
  425. }
  426. static IEnumerable<GameObject> IterateHierarchy(GameObject root)
  427. {
  428. yield return root;
  429. foreach (Transform child in root.transform)
  430. {
  431. foreach (GameObject subChild in IterateHierarchy(child.gameObject))
  432. {
  433. yield return subChild;
  434. }
  435. }
  436. }
  437. static Vector2 GetCurrentResolution()
  438. {
  439. #if UNITY_EDITOR
  440. FindAndStoreGameView();
  441. System.Reflection.MethodInfo GetSizeOfMainGameView = gameViewType.GetMethod("GetSizeOfMainGameView",
  442. System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
  443. object res = GetSizeOfMainGameView.Invoke(null, null);
  444. return (Vector2)res;
  445. #else
  446. return new Vector2(Screen.width, Screen.height);
  447. #endif
  448. }
  449. float GetCurrentDpi()
  450. {
  451. #if UNITY_EDITOR
  452. if (IsZoomPossible())
  453. {
  454. Vector2 scale = Vector2.one;
  455. FindAndStoreGameView();
  456. if (gameViewWindow != null)
  457. {
  458. var zoomArea = gameViewType.GetField("m_ZoomArea",
  459. System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)
  460. .GetValue(gameViewWindow);
  461. scale = (Vector2)zoomArea.GetType().GetField("m_Scale",
  462. System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)
  463. .GetValue(zoomArea);
  464. }
  465. return Screen.dpi / scale.y;
  466. }
  467. #endif
  468. return dpiManager.GetDpi();
  469. }
  470. #if UNITY_EDITOR
  471. static void FindAndStoreGameView()
  472. {
  473. if (gameViewType == null)
  474. {
  475. gameViewType = Type.GetType("UnityEditor.GameView,UnityEditor");
  476. }
  477. if (gameViewWindow == null)
  478. {
  479. gameViewWindow = Resources.FindObjectsOfTypeAll(gameViewType)
  480. .FirstOrDefault() as UnityEditor.EditorWindow;
  481. }
  482. }
  483. public static bool IsZoomPossible()
  484. {
  485. #if UNITY_2018_3_OR_NEWER // minimum officially supported version
  486. return true;
  487. #else
  488. return unityVersion.Major > 5
  489. || (unityVersion.Major == 5 && unityVersion.Minor >= 4);
  490. #endif
  491. }
  492. public void SetOptimizedResolutionFallback(Vector2 resolution)
  493. {
  494. this.optimizedResolutionFallback = resolution;
  495. }
  496. #endif
  497. }
  498. }
  499. #pragma warning restore 0618