Skip to content

Instantly share code, notes, and snippets.

@mandarinx
Forked from QubitsDev/ScrollRectAutoScroll.cs
Created August 22, 2017 11:30
Show Gist options
  • Save mandarinx/eae10c9e8d1a5534b7b19b74aeb2a665 to your computer and use it in GitHub Desktop.
Save mandarinx/eae10c9e8d1a5534b7b19b74aeb2a665 to your computer and use it in GitHub Desktop.
Unity3d ScrollRect Auto-Scroll, Dropdown Use: Places this component in the Template of Dropdown (with the ScrollRect component)
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);
}
}
@Nebuchaddy
Copy link

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.

@mandarinx
Copy link
Author

If the technique still applies, it's no necro in my opinion. Glad it's been of use to all of you. :-)

@Nebuchaddy
Copy link

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.

@mandarinx
Copy link
Author

Ah! Good catch. It totally agree on letting UI run on a separate timer from game time.

@gaetandezeiraud
Copy link

@Nebuchaddy Can you share your rewrite with the new Input System?

@emredesu
Copy link

emredesu commented Jan 17, 2022

@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

@PostleService
Copy link

Amazing how it still works after 5 years <3

@mandarinx
Copy link
Author

@PostleService That's because Unity hasn't touched their own UI system in 5 years. :-)

@PostleService
Copy link

@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

@Circled321
Copy link

I created an account just to thank you for this script. I tried lots of things before finding this and none of them worked.

@jepalfer
Copy link

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 :)

@j-schoch
Copy link

j-schoch commented Sep 27, 2024

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)));
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment