-
-
Save mandarinx/eae10c9e8d1a5534b7b19b74aeb2a665 to your computer and use it in GitHub Desktop.
using System.Collections.Generic; | |
using UnityEngine; | |
using UnityEngine.EventSystems; | |
using UnityEngine.UI; | |
[RequireComponent(typeof(ScrollRect))] | |
public class ScrollRectAutoScroll : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler | |
{ | |
public float scrollSpeed = 10f; | |
private bool mouseOver = false; | |
private List<Selectable> m_Selectables = new List<Selectable>(); | |
private ScrollRect m_ScrollRect; | |
private Vector2 m_NextScrollPosition = Vector2.up; | |
void OnEnable() | |
{ | |
if (m_ScrollRect) | |
{ | |
m_ScrollRect.content.GetComponentsInChildren(m_Selectables); | |
} | |
} | |
void Awake() | |
{ | |
m_ScrollRect = GetComponent<ScrollRect>(); | |
} | |
void Start() | |
{ | |
if (m_ScrollRect) | |
{ | |
m_ScrollRect.content.GetComponentsInChildren(m_Selectables); | |
} | |
ScrollToSelected(true); | |
} | |
void Update() | |
{ | |
// Scroll via input. | |
InputScroll(); | |
if (!mouseOver) | |
{ | |
// Lerp scrolling code. | |
m_ScrollRect.normalizedPosition = Vector2.Lerp(m_ScrollRect.normalizedPosition, m_NextScrollPosition, scrollSpeed * Time.deltaTime); | |
} | |
else | |
{ | |
m_NextScrollPosition = m_ScrollRect.normalizedPosition; | |
} | |
} | |
void InputScroll() | |
{ | |
if (m_Selectables.Count > 0) | |
{ | |
if (Input.GetButtonDown("Horizontal") || Input.GetButtonDown("Vertical") || Input.GetButton("Horizontal") || Input.GetButton("Vertical")) | |
{ | |
ScrollToSelected(false); | |
} | |
} | |
} | |
void ScrollToSelected(bool quickScroll) | |
{ | |
int selectedIndex = -1; | |
Selectable selectedElement = EventSystem.current.currentSelectedGameObject ? EventSystem.current.currentSelectedGameObject.GetComponent<Selectable>() : null; | |
if (selectedElement) | |
{ | |
selectedIndex = m_Selectables.IndexOf(selectedElement); | |
} | |
if (selectedIndex > -1) | |
{ | |
if (quickScroll) | |
{ | |
m_ScrollRect.normalizedPosition = new Vector2(0, 1 - (selectedIndex / ((float)m_Selectables.Count - 1))); | |
m_NextScrollPosition = m_ScrollRect.normalizedPosition; | |
} | |
else | |
{ | |
m_NextScrollPosition = new Vector2(0, 1 - (selectedIndex / ((float)m_Selectables.Count - 1))); | |
} | |
} | |
} | |
public void OnPointerEnter(PointerEventData eventData) | |
{ | |
mouseOver = true; | |
} | |
public void OnPointerExit(PointerEventData eventData) | |
{ | |
mouseOver = false; | |
ScrollToSelected(false); | |
} | |
} |
I know this is a bit necro af but you saved my life. Had to rewrite just a bit for the new Input System (line 52) but other than that it works like a charm. For real, thanks a bunch.
If the technique still applies, it's no necro in my opinion. Glad it's been of use to all of you. :-)
If the technique still applies, it's no necro in my opinion. Glad it's been of use to all of you. :-)
Yeah, works great. Though I just spent like 30 mins trying to understand why it was working on my main menu but not on my pause menu, then I saw the Lerp function, and well, can't use Time.deltaTime if Time.timeScale is 0, so i just changed it to Time.unscaledDeltaTime, which I think is much better for UI in general, just in case.
Anyways, thanks a bunch again, i've spent hours and all I found were methods involving the dropdown component, 300 lines code, or over-complicated stuff. This is much more elegant, easy to read and understan, and really easy to implement.
Again, thanks.
Ah! Good catch. It totally agree on letting UI run on a separate timer from game time.
@Nebuchaddy Can you share your rewrite with the new Input System?
@Brouilles A bit late, but I created a fork for this in case anyone else needs it. I've tested it on PC with a keyboard, an Xbox One gamepad and on an Android phone, it worked fine on all of them.
https://gist.github.com/emredesu/af597de14a4377e1ecf96b6f7b6cc506
Amazing how it still works after 5 years <3
@PostleService That's because Unity hasn't touched their own UI system in 5 years. :-)
@mandarinx I got back to the code because it confused me why it wouldn't actually center on the selected option on option change.
Imho, You may be missing:
m_ScrollRect.normalizedPosition = m_NextScrollPosition;
after line 76
since You set the new m_NextScrollPosition but never tell the RectTransform to use the new position after the input
I created an account just to thank you for this script. I tried lots of things before finding this and none of them worked.
For anyone who has the same issue as me, I had to add this else in the InputScroll method:
else
{
ScrollToSelected(true);
}
The issue happened when this script was attatched to the template of a TMP_Dropdown which is part of a prefab that gets instantiated in runtime, it wouldn't scroll when I pressed down or up, but that else fixed it :)
I stripped this down to instantly scroll to the selected item when the dropdown opens and nothing else.
Figured I'd share it and maybe save someone some time:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
[RequireComponent(typeof(ScrollRect))]
public class ScrollRectAutoScroll : MonoBehaviour
{
private readonly List<Selectable> _selectables = new();
private ScrollRect _scrollRect;
private void Awake()
{
_scrollRect = GetComponent<ScrollRect>();
}
private void Start()
{
ScrollToSelected();
}
private void ScrollToSelected()
{
if (_scrollRect == null) { return; }
_scrollRect.content.GetComponentsInChildren(_selectables);
if(_selectables.Count <= 1) { return; }
GameObject selected = EventSystem.current.currentSelectedGameObject;
if (selected != null && selected.TryGetComponent(out Selectable selectedElement))
{
int selectedIndex = _selectables.IndexOf(selectedElement);
_scrollRect.normalizedPosition = new Vector2(0, 1 - (selectedIndex / ((float)_selectables.Count - 1)));
}
}
}
Life saver script! Thanks a lot.
Remarks:
I like the behavior better without the line 87: ScrollToSelected(false);
This makes the mouse scroll works better.