AutoLayoutElement.cs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. using System;
  2. using UnityEngine;
  3. using System.Collections.Generic;
  4. #if UNITY_EDITOR
  5. [ExecuteInEditMode()]
  6. #endif
  7. public class AutoLayoutElement:MonoBehaviour {
  8. // Delegates
  9. protected delegate void SimpleAction();
  10. // Events
  11. protected event SimpleAction OnShouldApplyLayout;
  12. // Properties
  13. private List<GameObject> dependents = new List<GameObject>();
  14. private List<GameObject> parents = new List<GameObject>();
  15. private float lastCameraHeight;
  16. private float lastCameraWidth;
  17. protected bool shouldAutoUpdate; // If true, this instance should self-update when a change was detected (otherwise, it only updates when told so by parents)
  18. private bool needsAdditionalUpdate;
  19. // ================================================================================================================
  20. // MAIN EVENT INTERFACE -------------------------------------------------------------------------------------------
  21. void Awake() {
  22. }
  23. void Start() {
  24. applyLayoutRules();
  25. }
  26. void Update() {
  27. if (needsAdditionalUpdate) {
  28. // This should not be needed, but it's always missing 1 frame at the end
  29. applyLayoutRules();
  30. needsAdditionalUpdate = false;
  31. }
  32. }
  33. void LateUpdate() {
  34. if (shouldAutoUpdate) {
  35. // Should auto update: checks if necessary
  36. if (Camera.main.pixelHeight != lastCameraHeight || Camera.main.pixelWidth != lastCameraWidth) {
  37. // Needs to update
  38. //Debug.Log("=============> Updating starting @ " + transform.parent.gameObject.name + "." + gameObject.name);
  39. lastCameraHeight = Camera.main.pixelHeight;
  40. lastCameraWidth = Camera.main.pixelWidth;
  41. applyLayoutRules();
  42. needsAdditionalUpdate = true;
  43. }
  44. }
  45. }
  46. /*
  47. void OnEnable() {
  48. if (shouldAutoUpdate) needsUpdate = true;
  49. }
  50. */
  51. // ================================================================================================================
  52. // PUBLIC INTERFACE -----------------------------------------------------------------------------------------------
  53. public void requestUpdate() {
  54. applyLayoutRules();
  55. }
  56. public void addDependent(GameObject dependent) {
  57. if (!dependents.Contains(dependent)) dependents.Add(dependent);
  58. }
  59. public void removeDependent(GameObject dependent) {
  60. if (dependents.Contains(dependent)) dependents.Remove(dependent);
  61. }
  62. // ================================================================================================================
  63. // INTERNAL INTERFACE ---------------------------------------------------------------------------------------------
  64. protected void checkParents(params GameObject[] parentsToCheck) {
  65. // Checks if a parent has changed, adding or removing itself from the parent as a dependent
  66. var addedParents = new List<GameObject>();
  67. var removedParents = new List<GameObject>();
  68. // Checks differences
  69. // Checks for new parents
  70. foreach (var parentToCheck in parentsToCheck) {
  71. if (parentToCheck == gameObject) {
  72. Debug.LogWarning("Trying to add itself as a parent!");
  73. continue;
  74. }
  75. if (parentToCheck == null) continue;
  76. if (!parents.Contains(parentToCheck) && !addedParents.Contains(parentToCheck)) {
  77. addedParents.Add(parentToCheck);
  78. }
  79. }
  80. // Checks for removed parents
  81. foreach (var parent in parents) {
  82. if (Array.IndexOf(parentsToCheck, parent) < 0 && !removedParents.Contains(parent)) {
  83. removedParents.Add(parent);
  84. }
  85. }
  86. // Finally, adds and removes all parent
  87. foreach (var parent in addedParents) {
  88. addToParent(parent);
  89. parents.Add(parent);
  90. }
  91. foreach (var parent in removedParents) {
  92. removeFromParent(parent);
  93. parents.Remove(parent);
  94. }
  95. }
  96. protected Bounds getBoundsOrPoint(GameObject gameObject) {
  97. // Gets the best bounding box from a game object, using a placeholder box if none can be found
  98. var bounds = getBounds(gameObject);
  99. return bounds == null ? new Bounds(gameObject.transform.position, new Vector3(0.0001f, 0.0001f, 0.0001f)) : (Bounds)bounds;
  100. }
  101. protected Bounds? getBounds(GameObject gameObject) {
  102. // Gets the best bounding box from a game object
  103. if (gameObject == null) {
  104. // No object, use the screen size instead
  105. var tl = Camera.main.ScreenToWorldPoint(new Vector3(0, Camera.main.pixelHeight, 0.1f));
  106. var br = Camera.main.ScreenToWorldPoint(new Vector3(Camera.main.pixelWidth, 0, 0.1f));
  107. return new Bounds((tl+br)/2, br-tl);
  108. } else if (gameObject.GetComponent<Collider>() != null) {
  109. // Use collider
  110. return gameObject.GetComponent<Collider>().bounds;
  111. } else if (gameObject.GetComponent<Renderer>() != null) {
  112. // Use renderer
  113. return gameObject.GetComponent<Renderer>().bounds;
  114. }
  115. // None found!
  116. return null;
  117. }
  118. // ================================================================================================================
  119. // INTERNAL INTERFACE ---------------------------------------------------------------------------------------------
  120. private void removeFromParent(GameObject parent) {
  121. if (parent != null && parent.GetComponent<AutoLayoutElement>() != null) {
  122. var autoLayoutElements = parent.GetComponents<AutoLayoutElement>();
  123. foreach (var autoLayoutElement in autoLayoutElements) autoLayoutElement.removeDependent(gameObject);
  124. }
  125. }
  126. private void addToParent(GameObject parent) {
  127. if (parent != null && parent.GetComponent<AutoLayoutElement>() != null) {
  128. var autoLayoutElements = parent.GetComponents<AutoLayoutElement>();
  129. foreach (var autoLayoutElement in autoLayoutElements) autoLayoutElement.addDependent(gameObject);
  130. }
  131. }
  132. private void applyLayoutRules() {
  133. //needsUpdate = false;
  134. //Debug.Log("Applying update @ frame " + Time.frameCount + ", " + transform.parent.gameObject.name + "." + gameObject.name + ", with " + dependents.Count + " dependents");
  135. // Applies layout to self
  136. if (OnShouldApplyLayout != null) {
  137. OnShouldApplyLayout();
  138. }
  139. // Propagates to dependents
  140. var destroyedDependents = new List<GameObject>();
  141. foreach (var dependent in dependents) {
  142. if (dependent != null && dependent.activeInHierarchy) {
  143. var autoLayoutElements = dependent.GetComponents<AutoLayoutElement>();
  144. foreach (var autoLayoutElement in autoLayoutElements) autoLayoutElement.requestUpdate();
  145. } else {
  146. // Object has been destroyed!
  147. Debug.LogWarning("Dependent object was destroyed during gameplay, will remove it from list of dependents");
  148. destroyedDependents.Add(dependent);
  149. }
  150. }
  151. foreach (var dependent in destroyedDependents) {
  152. dependents.Remove(dependent);
  153. }
  154. }
  155. }