Last active
March 26, 2025 22:52
-
-
Save lmartim/fe5ff3f133c24948838223352b06303f to your computer and use it in GitHub Desktop.
Auto scroll vertically a ScrollRect when selecting a component outside the viewport
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://www.reddit.com/r/Unity3D/comments/1epw3u6/need_some_help_with_scroll_view_auto_scroll/ | |
using System.Collections; | |
using System.Collections.Generic; | |
using TMPro; | |
using UnityEngine; | |
using UnityEngine.EventSystems; | |
using UnityEngine.UI; | |
public class ScrollRectAutoScroll : MonoBehaviour | |
{ | |
[SerializeField] private ScrollRect scrollRect; | |
[SerializeField] private GameObject cardPrefab; | |
private RectTransform contentRect; | |
private List<GameObject> elements; | |
private void Start() | |
{ | |
//get the scroll rect components | |
scrollRect = GetComponent<ScrollRect>(); | |
contentRect = scrollRect.content; | |
//creates the list of cards, can also be a reference of your deck list | |
elements = new List<GameObject>(); | |
for (int k = 0; k < 15; k++) | |
{ | |
GameObject go = Instantiate(cardPrefab, contentRect.transform); | |
go.GetComponentInChildren<TextMeshProUGUI>().text = k.ToString(); | |
elements.Add(go); | |
} | |
//add a listener that invokes BringChildIntoViewVertically every time a card is selected | |
//the card prefab needs to have a EventTrigger component | |
foreach (GameObject go in elements) | |
{ | |
EventTrigger eventTrigger = go.GetComponent<EventTrigger>(); | |
EventTrigger.Entry entry = new EventTrigger.Entry(); | |
entry.eventID = EventTriggerType.Select; | |
entry.callback.AddListener((eventData) => { BringChildIntoViewVertically(go.GetComponent<RectTransform>()); }); | |
eventTrigger.triggers.Add(entry); | |
} | |
scrollRect.horizontalNormalizedPosition = 0; | |
EventSystem.current.SetSelectedGameObject(elements[0]); | |
} | |
public void BringChildIntoViewVertically(RectTransform child) | |
{ | |
//if vertical scrollbar isn't visible, return | |
if (!scrollRect.verticalScrollbar.gameObject.activeSelf) | |
return; | |
scrollRect.content.ForceUpdateRectTransforms(); | |
scrollRect.viewport.ForceUpdateRectTransforms(); | |
// now takes scaling into account | |
Vector2 viewportLocalPosition = scrollRect.viewport.localPosition; | |
Vector2 childLocalPosition = child.localPosition; | |
Vector2 newContentPosition = new Vector2( | |
0 - ((viewportLocalPosition.x * scrollRect.viewport.localScale.x) + (childLocalPosition.x * scrollRect.content.localScale.x)), | |
0 - ((viewportLocalPosition.y * scrollRect.viewport.localScale.y) + (childLocalPosition.y * scrollRect.content.localScale.y)) | |
); | |
// clamp positions | |
scrollRect.content.localPosition = newContentPosition; | |
Rect contentRectInViewport = TransformRectFromTo(scrollRect.content.transform, scrollRect.viewport); | |
float deltaXMin = contentRectInViewport.xMin - scrollRect.viewport.rect.xMin; | |
if (deltaXMin > 0) // clamp to <= 0 | |
newContentPosition.x -= deltaXMin; | |
float deltaXMax = contentRectInViewport.xMax - scrollRect.viewport.rect.xMax; | |
if (deltaXMax < 0) // clamp to >= 0 | |
newContentPosition.x -= deltaXMax; | |
float deltaYMin = contentRectInViewport.yMin - scrollRect.viewport.rect.yMin; | |
if (deltaYMin > 0) // clamp to <= 0 | |
newContentPosition.y -= deltaYMin; | |
float deltaYMax = contentRectInViewport.yMax - scrollRect.viewport.rect.yMax; | |
if (deltaYMax < 0) // clamp to >= 0 | |
newContentPosition.y -= deltaYMax; | |
// apply final position | |
scrollRect.content.localPosition = newContentPosition; | |
scrollRect.content.ForceUpdateRectTransforms(); | |
} | |
public static Rect TransformRectFromTo(Transform from, Transform to) | |
{ | |
RectTransform fromRectTrans = from.GetComponent<RectTransform>(); | |
RectTransform toRectTrans = to.GetComponent<RectTransform>(); | |
if (fromRectTrans != null && toRectTrans != null) | |
{ | |
Vector3[] fromWorldCorners = new Vector3[4]; | |
Vector3[] toLocalCorners = new Vector3[4]; | |
Matrix4x4 toLocal = to.worldToLocalMatrix; | |
fromRectTrans.GetWorldCorners(fromWorldCorners); | |
for (int i = 0; i < 4; i++) | |
toLocalCorners[i] = toLocal.MultiplyPoint3x4(fromWorldCorners[i]); | |
return new Rect(toLocalCorners[0].x, toLocalCorners[0].y, toLocalCorners[2].x - toLocalCorners[1].x, | |
toLocalCorners[1].y - toLocalCorners[0].y); | |
} | |
return default(Rect); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment