2
0

UIScrollToSelection.cs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. /// Credit zero3growlithe
  2. /// sourced from: http://forum.unity3d.com/threads/scripts-useful-4-6-scripts-collection.264161/page-2#post-2011648
  3. /*USAGE:
  4. Simply place the script on the ScrollRect that contains the selectable children you will be scrolling
  5. */
  6. using System;
  7. using System.Collections;
  8. using UnityEngine.EventSystems;
  9. namespace UnityEngine.UI.Extensions
  10. {
  11. [RequireComponent(typeof(ScrollRect))]
  12. [AddComponentMenu("UI/Extensions/UIScrollToSelection")]
  13. public class UIScrollToSelection : MonoBehaviour
  14. {
  15. #region MEMBERS
  16. [Header("[ References ]")]
  17. [SerializeField, Tooltip("View (boundaries/mask) rect transform. Used to check if automatic scroll to selection is required.")]
  18. private RectTransform viewportRectTransform;
  19. [SerializeField, Tooltip("Scroll rect used to reach selected element.")]
  20. private ScrollRect targetScrollRect;
  21. [Header("[ Scrolling ]")]
  22. [SerializeField, Tooltip("Allow automatic scrolling only on these axes.")]
  23. private Axis scrollAxes = Axis.ANY;
  24. [SerializeField, Tooltip("MOVE_TOWARDS: stiff movement, LERP: smoothed out movement")]
  25. private ScrollMethod usedScrollMethod = ScrollMethod.MOVE_TOWARDS;
  26. [SerializeField]
  27. private float scrollSpeed = 50;
  28. [Space(5)]
  29. [SerializeField, Tooltip("Scroll speed used when element to select is out of \"JumpOffsetThreshold\" range")]
  30. private float endOfListJumpScrollSpeed = 150;
  31. [SerializeField, Range(0, 1), Tooltip("If next element to scroll to is located over this screen percentage, use \"EndOfListJumpScrollSpeed\" to reach this element faster.")]
  32. private float jumpOffsetThreshold = 1;
  33. [Header("[ Input ]")]
  34. [SerializeField]
  35. private MouseButton cancelScrollMouseButtons = MouseButton.ANY;
  36. [SerializeField]
  37. private KeyCode[] cancelScrollKeys = new KeyCode[0];
  38. // INTERNAL - MEMBERS ONLY
  39. private Vector3[] viewRectCorners = new Vector3[4];
  40. private Vector3[] selectedElementCorners = new Vector3[4];
  41. #endregion
  42. #region PROPERTIES
  43. // REFERENCES
  44. public RectTransform ViewRectTransform
  45. {
  46. get { return viewportRectTransform; }
  47. set { viewportRectTransform = value; }
  48. }
  49. public ScrollRect TargetScrollRect
  50. {
  51. get { return targetScrollRect; }
  52. set { targetScrollRect = value; }
  53. }
  54. // SCROLLING
  55. public Axis ScrollAxes => scrollAxes;
  56. public ScrollMethod UsedScrollMethod => usedScrollMethod;
  57. public float ScrollSpeed => scrollSpeed;
  58. public float EndOfListJumpScrollSpeed => endOfListJumpScrollSpeed;
  59. public float JumpOffsetThreshold => jumpOffsetThreshold;
  60. // INPUT
  61. public MouseButton CancelScrollMouseButtons => cancelScrollMouseButtons;
  62. public KeyCode[] CancelScrollKeys => cancelScrollKeys;
  63. // VARIABLES
  64. private RectTransform scrollRectContentTransform;
  65. private GameObject lastCheckedSelection;
  66. // COROUTINES
  67. private Coroutine animationCoroutine;
  68. #endregion
  69. #region FUNCTIONS
  70. protected void Awake()
  71. {
  72. ValidateReferences();
  73. }
  74. protected void LateUpdate()
  75. {
  76. TryToScrollToSelection();
  77. }
  78. protected void Reset()
  79. {
  80. TargetScrollRect = gameObject.GetComponentInParent<ScrollRect>() ?? gameObject.GetComponentInChildren<ScrollRect>();
  81. ViewRectTransform = gameObject.GetComponent<RectTransform>();
  82. }
  83. private void ValidateReferences()
  84. {
  85. if (!targetScrollRect)
  86. {
  87. targetScrollRect = GetComponent<ScrollRect>();
  88. }
  89. if (!targetScrollRect)
  90. {
  91. Debug.LogError("[UIScrollToSelection] No ScrollRect found. Either attach this script to a ScrollRect or assign on in the 'Target Scroll Rect' property");
  92. gameObject.SetActive(false);
  93. return;
  94. }
  95. if (ViewRectTransform == null)
  96. {
  97. ViewRectTransform = TargetScrollRect.GetComponent<RectTransform>();
  98. }
  99. if (TargetScrollRect != null)
  100. {
  101. scrollRectContentTransform = TargetScrollRect.content;
  102. }
  103. if (EventSystem.current == null)
  104. {
  105. Debug.LogError("[UIScrollToSelection] Unity UI EventSystem not found. It is required to check current selected object.");
  106. gameObject.SetActive(false);
  107. return;
  108. }
  109. }
  110. private void TryToScrollToSelection()
  111. {
  112. // update references if selection changed
  113. GameObject selection = EventSystem.current.currentSelectedGameObject;
  114. if (selection == null || selection.activeInHierarchy == false || selection == lastCheckedSelection ||
  115. selection.transform.IsChildOf(transform) == false)
  116. {
  117. return;
  118. }
  119. RectTransform selectionRect = selection.GetComponent<RectTransform>();
  120. ViewRectTransform.GetWorldCorners(viewRectCorners);
  121. selectionRect.GetWorldCorners(selectedElementCorners);
  122. ScrollToSelection(selection);
  123. lastCheckedSelection = selection;
  124. }
  125. private void ScrollToSelection(GameObject selection)
  126. {
  127. // initial check if we can scroll at all
  128. if (selection == null)
  129. {
  130. return;
  131. }
  132. // this is just to make names shorter a bit
  133. Vector3[] corners = viewRectCorners;
  134. Vector3[] selectionCorners = selectedElementCorners;
  135. // calculate scroll offset
  136. Vector2 offsetToSelection = Vector2.zero;
  137. offsetToSelection.x =
  138. (selectionCorners[0].x < corners[0].x ? selectionCorners[0].x - corners[0].x : 0) +
  139. (selectionCorners[2].x > corners[2].x ? selectionCorners[2].x - corners[2].x : 0);
  140. offsetToSelection.y =
  141. (selectionCorners[0].y < corners[0].y ? selectionCorners[0].y - corners[0].y : 0) +
  142. (selectionCorners[1].y > corners[1].y ? selectionCorners[1].y - corners[1].y : 0);
  143. // calculate final scroll speed
  144. float finalScrollSpeed = ScrollSpeed;
  145. if (Math.Abs(offsetToSelection.x) / Screen.width >= JumpOffsetThreshold || Math.Abs(offsetToSelection.y) / Screen.height >= JumpOffsetThreshold)
  146. {
  147. finalScrollSpeed = EndOfListJumpScrollSpeed;
  148. }
  149. // initiate animation coroutine
  150. Vector2 targetPosition = (Vector2)scrollRectContentTransform.localPosition - offsetToSelection;
  151. if (animationCoroutine != null)
  152. {
  153. StopCoroutine(animationCoroutine);
  154. }
  155. animationCoroutine = StartCoroutine(ScrollToPosition(targetPosition, finalScrollSpeed));
  156. }
  157. private IEnumerator ScrollToPosition(Vector2 targetPosition, float speed)
  158. {
  159. Vector3 startPosition = scrollRectContentTransform.localPosition;
  160. // cancel movement on axes not specified in ScrollAxes mask
  161. targetPosition.x = ((ScrollAxes | Axis.HORIZONTAL) == ScrollAxes) ? targetPosition.x : startPosition.x;
  162. targetPosition.y = ((ScrollAxes | Axis.VERTICAL) == ScrollAxes) ? targetPosition.y : startPosition.y;
  163. // move to target position
  164. Vector2 currentPosition2D = startPosition;
  165. float horizontalSpeed = (Screen.width / Screen.dpi) * speed;
  166. float verticalSpeed = (Screen.height / Screen.dpi) * speed;
  167. while (currentPosition2D != targetPosition && CheckIfScrollInterrupted() == false)
  168. {
  169. currentPosition2D.x = MoveTowardsValue(currentPosition2D.x, targetPosition.x, horizontalSpeed, UsedScrollMethod);
  170. currentPosition2D.y = MoveTowardsValue(currentPosition2D.y, targetPosition.y, verticalSpeed, UsedScrollMethod);
  171. scrollRectContentTransform.localPosition = currentPosition2D;
  172. yield return null;
  173. }
  174. scrollRectContentTransform.localPosition = currentPosition2D;
  175. }
  176. private bool CheckIfScrollInterrupted()
  177. {
  178. bool mouseButtonClicked = false;
  179. // check mouse buttons
  180. switch (CancelScrollMouseButtons)
  181. {
  182. case MouseButton.LEFT:
  183. mouseButtonClicked |= Input.GetMouseButtonDown(0);
  184. break;
  185. case MouseButton.RIGHT:
  186. mouseButtonClicked |= Input.GetMouseButtonDown(1);
  187. break;
  188. case MouseButton.MIDDLE:
  189. mouseButtonClicked |= Input.GetMouseButtonDown(2);
  190. break;
  191. }
  192. if (mouseButtonClicked == true)
  193. {
  194. return true;
  195. }
  196. // check keyboard buttons
  197. for (int i = 0; i < CancelScrollKeys.Length; i++)
  198. {
  199. if (Input.GetKeyDown(CancelScrollKeys[i]) == true)
  200. {
  201. return true;
  202. }
  203. }
  204. return false;
  205. }
  206. private float MoveTowardsValue(float from, float to, float delta, ScrollMethod method)
  207. {
  208. switch (method)
  209. {
  210. case ScrollMethod.MOVE_TOWARDS:
  211. return Mathf.MoveTowards(from, to, delta * Time.unscaledDeltaTime);
  212. case ScrollMethod.LERP:
  213. return Mathf.Lerp(from, to, delta * Time.unscaledDeltaTime);
  214. default:
  215. return from;
  216. }
  217. }
  218. #endregion
  219. #region CLASS_ENUMS
  220. [Flags]
  221. public enum Axis
  222. {
  223. NONE = 0x00000000,
  224. HORIZONTAL = 0x00000001,
  225. VERTICAL = 0x00000010,
  226. ANY = 0x00000011
  227. }
  228. [Flags]
  229. public enum MouseButton
  230. {
  231. NONE = 0x00000000,
  232. LEFT = 0x00000001,
  233. RIGHT = 0x00000010,
  234. MIDDLE = 0x00000100,
  235. ANY = 0x00000111
  236. }
  237. public enum ScrollMethod
  238. {
  239. MOVE_TOWARDS,
  240. LERP
  241. }
  242. #endregion
  243. }
  244. }