Unity - Dropdown scroll using controller or keyboard

I have a UI dropdown (Text mesh pro in my case, but should not matter). This dropdown works perfectly with my mouse, but if I try to select values using my controller/gamepad or my keyboard, it does not scroll to the selected value.

How can I get it to work as you expect when using a controller, i.e the scrollrect will follow the selected element.

I also have this problem, it seems Unity doesn’t have a solution.

I also have this problem!

Did you check if the navigation on the sub-items is set to AUTOMATIC ?

1 Like

Quick and dirty solution

using System;
using Extensions.Unity;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.UI;

namespace UI
{
  [RequireComponent(typeof(ScrollRect))]
  public class ScrollRectAutoScroll : MonoBehaviour
  {
    [Tooltip("The UI/Navigate action used by controller and keyboard to use menus")]
    [SerializeField] InputActionReference navigateAction;
    /// <summary>
    /// The scrollRect attached to this component
    /// </summary>
    ScrollRect _scrollRect;
    /// <summary>
    /// All selectable items inside the scrollrect
    /// </summary>
    Selectable[] _selectables;

    public void Start()
    {
      _scrollRect = GetComponent<ScrollRect>();
      _selectables = GetComponentsInChildren<Selectable>();
      ScrollToSelection();
    }

    void ScrollToSelection()
    {
      GameObject selection = EventSystem.current.currentSelectedGameObject;
      if (!selection) return;
      if (!selection.transform.IsChildOf(transform)) return;
      if (!selection.TryGetComponent(out RectTransform rectTransform)) return;
      _scrollRect.ScrollToCenter(rectTransform);
      int index = Array.IndexOf(_selectables, rectTransform);
      if (index < 0) return;
      float target = 1f - (float)index / Mathf.Max(_selectables.Length - 2, 1);
      _scrollRect.verticalNormalizedPosition = target;
    }

    public void Update()
    {
      if (navigateAction.action.inProgress)
        ScrollToSelection();
    }
  }
}

I know this is a bit old, but I’m trying to use this solution. I do not however have whatever package “Extensions.Unity” is in my project. Could you point me to that package, or perhaps simply to the ScrollToCenter extension method?

Remove it, after reading the code I can’t see where it’s being used.

line 37:

_scrollRect.ScrollToCenter(rectTransform);

ScrollToCenter is not a method that exists on ScrollRect, so I assumed it was an extension method defined in that package.

1 Like

Quite sorry, here’s the extension methods:

using UnityEngine;
using UnityEngine.UI;
// https://gist.github.com/sttz/c406aec3ace821738ecd4fa05833d21d
namespace UI
{
    public static class UIExtensions {
        // Shared array used to receive result of RectTransform.GetWorldCorners
        static readonly Vector3[] Corners = new Vector3[4];

        /// <summary>
        /// Transform the bounds of the current rect transform to the space of another transform.
        /// </summary>
        /// <param name="source">The rect to transform</param>
        /// <param name="target">The target space to transform to</param>
        /// <returns>The transformed bounds</returns>
        // ReSharper disable once MemberCanBePrivate.Global
        public static Bounds TransformBoundsTo(this RectTransform source, Transform target)
        {
            // Based on code in ScrollRect internal GetBounds and InternalGetBounds methods
            Bounds bounds = new Bounds();
            if (source != null) {
                source.GetWorldCorners(Corners);

                Vector3 vMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
                Vector3 vMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);

                Matrix4x4 matrix = target.worldToLocalMatrix;
                for (int j = 0; j < 4; j++) {
                    Vector3 v = matrix.MultiplyPoint3x4(Corners[j]);
                    vMin = Vector3.Min(v, vMin);
                    vMax = Vector3.Max(v, vMax);
                }

                bounds = new Bounds(vMin, Vector3.zero);
                bounds.Encapsulate(vMax);
            }
            return bounds;
        }

        /// <summary>
        /// Normalize a distance to be used in verticalNormalizedPosition or horizontalNormalizedPosition.
        /// </summary>
        /// <param name="scrollRect">The target scrollRect</param>
        /// <param name="axis">Scroll axis, 0 = horizontal, 1 = vertical</param>
        /// <param name="distance">The distance in the scroll rect view coordinate space</param>
        /// <returns>The normalized scroll distance</returns>
        // ReSharper disable once MemberCanBePrivate.Global
        public static float NormalizeScrollDistance(this ScrollRect scrollRect, int axis, float distance)
        {
            // Based on code in ScrollRect internal SetNormalizedPosition method
            RectTransform viewport = scrollRect.viewport;
            RectTransform viewRect = viewport != null ? viewport : scrollRect.GetComponent<RectTransform>();
            Rect rect = viewRect.rect;
            Bounds viewBounds = new Bounds(rect.center, rect.size);

            RectTransform content = scrollRect.content;
            Bounds contentBounds = content != null ? content.TransformBoundsTo(viewRect) : new Bounds();

            float hiddenLength = contentBounds.size[axis] - viewBounds.size[axis];
            return distance / hiddenLength;
        }

        /// <summary>
        /// Scroll the target element to the vertical center of the scroll rect viewport.
        /// Assumes the target element is part of the scroll rect contents.
        /// </summary>
        /// <param name="scrollRect">Scroll rect to scroll</param>
        /// <param name="target">Element of the scroll rect content to center vertically</param>
        public static void ScrollToCenter(this ScrollRect scrollRect, RectTransform target)
        {
            // The scroll rect view space is used to calculate scroll position
            RectTransform view = scrollRect.viewport != null ? scrollRect.viewport : scrollRect.GetComponent<RectTransform>();

            // Calculate the scroll offset in the view's space
            Rect viewRect = view.rect;
            Bounds elementBounds = target.TransformBoundsTo(view);
            float offset = viewRect.center.y - elementBounds.center.y;

            // Normalize and apply the calculated offset
            float scrollPos = scrollRect.verticalNormalizedPosition - scrollRect.NormalizeScrollDistance(1, offset);
            scrollRect.verticalNormalizedPosition = Mathf.Clamp(scrollPos, 0f, 1f);
        }
    }
}

And the source: Method to center an element in a ScrollRect using Unity's new UI system · GitHub

1 Like