Home Game Development Unity – Autoscroll when selecting buttons out of viewport

Unity – Autoscroll when selecting buttons out of viewport

0
Unity – Autoscroll when selecting buttons out of viewport

[ad_1]

A more general solution:

  1. get the current selection
  2. check if it’s a child of the ScrollRect
  3. use a bit of math to scroll to it directly:

This works pretty well, and can be limited to only react to controller or keyboard navigation.

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


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;

    public void Start()
    {
      // Get the scrollRect
      _scrollRect = GetComponent<ScrollRect>();
      // Scroll to selection on init
      ScrollToSelection();
    }

    void ScrollToSelection()
    {
      // Get the selection
      GameObject selection = EventSystem.current.currentSelectedGameObject;
      // If no selection, return
      if (!selection) return;
      // If selection is not a child of the scrollRect, return
      if (!selection.transform.IsChildOf(transform)) return;
      // If somehow the selection is not a RectTransform, return
      if (!selection.TryGetComponent(out RectTransform rectTransform)) return;
      // Scroll into view
      _scrollRect.ScrollToCenter(rectTransform);
    }

    public void Update()
    {
      // If user has used the navigate buttons this frame, update scroll
      if (navigateAction.action.inProgress)
        ScrollToSelection();
    }
  }
}

[ad_2]