Unity UI ScrollRect Tools

Working on Nowhere Prophet I built two quick tools for the ScrollRect. I wanted to briefly post them here so maybe someone else can get some use out of them.

One called the ScrollRectTweener can take control over a ScrollRect and smoothly scroll it to a given normalized target position. If the player moves the view while the tweener is acting, then it aborts it’s movement, but you have the option to lock out the player drag when it is working.

The second is the ScrollRectLinker. This component can be put on one scrollRect to link its normalized position to the normalized position of another scrollRect. That way simple parallax scrolling can be quickly set up.

ScrollRectTweener.cs

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;

[RequireComponent(typeof(ScrollRect))]
public class ScrollRectTweener : MonoBehaviour, IDragHandler {

	ScrollRect scrollRect;
	Vector2 startPos;
	Vector2 targetPos;

	bool wasHorizontal;
	bool wasVertical;

	public float moveSpeed = 5000f;
	public bool disableDragWhileTweening = false;

	void Awake() {
		scrollRect = GetComponent<ScrollRect>();
		wasHorizontal = scrollRect.horizontal;
		wasVertical = scrollRect.vertical;
	}

	public void ScrollHorizontal(float normalizedX) {
		Scroll(new Vector2(normalizedX, scrollRect.verticalNormalizedPosition));
	}

	public void ScrollHorizontal(float normalizedX, float duration) {
		Scroll(new Vector2(normalizedX, scrollRect.verticalNormalizedPosition), duration);
	}
	
	public void ScrollVertical(float normalizedY) {
		Scroll(new Vector2(scrollRect.horizontalNormalizedPosition, normalizedY));
	}
	
	public void ScrollVertical(float normalizedY, float duration) {
		Scroll(new Vector2(scrollRect.horizontalNormalizedPosition, normalizedY), duration);
	}
	
	public void Scroll(Vector2 normalizedPos) {
		Scroll(normalizedPos, GetScrollDuration(normalizedPos) );
	}

	float GetScrollDuration(Vector2 normalizedPos) {		
		Vector2 currentPos = GetCurrentPos();
		return Vector2.Distance( DeNormalize(currentPos), DeNormalize(normalizedPos) ) / moveSpeed;
	}
	
	Vector2 DeNormalize(Vector2 normalizedPos) {
		return new Vector2(normalizedPos.x * scrollRect.content.rect.width, normalizedPos.y * scrollRect.content.rect.height);
	}

	Vector2 GetCurrentPos() {
		return new Vector2(scrollRect.horizontalNormalizedPosition, scrollRect.verticalNormalizedPosition);
	}
	
	public void Scroll(Vector2 normalizedPos, float duration) {
		startPos = GetCurrentPos();
		targetPos = normalizedPos;
		
		if (disableDragWhileTweening) 
			LockScrollability();
		
		StopAllCoroutines();
		StartCoroutine(DoMove(duration));	
	}

	IEnumerator DoMove(float duration) {

		// Abort if movement would be too short
		if (duration < 0.05f)
			yield break;

		Vector2 posOffset = targetPos - startPos;

		float currentTime = 0f;
		while (currentTime < duration) {
			currentTime += Time.deltaTime;
			scrollRect.normalizedPosition = EaseVector(currentTime, startPos, posOffset, duration);
			yield return null;
		}

		scrollRect.normalizedPosition = targetPos;

		if (disableDragWhileTweening) 
			RestoreScrollability();
	}

	public Vector2 EaseVector(float currentTime, Vector2 startValue, Vector2 changeInValue, float duration) {
		return new Vector2(
			changeInValue.x * Mathf.Sin(currentTime/duration * (Mathf.PI/2)) + startValue.x,
			changeInValue.y * Mathf.Sin(currentTime/duration * (Mathf.PI/2)) + startValue.y
			);
	}

	public void OnDrag(PointerEventData eventData) {
		if (!disableDragWhileTweening)
			StopScroll();
	}
	
	void StopScroll() {
		StopAllCoroutines();
		if (disableDragWhileTweening) 
			RestoreScrollability();
	}
	
	void LockScrollability() {
		scrollRect.horizontal = false;
		scrollRect.vertical = false;
	}
	
	void RestoreScrollability() {
		scrollRect.horizontal = wasHorizontal;
		scrollRect.vertical = wasVertical;
	}

}

ScrollRectLinker.cs

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;

[RequireComponent(typeof(ScrollRect))]
public class ScrollRectLinker : MonoBehaviour {

	public bool clamp = true;

	[SerializeField] ScrollRect controllingScrollRect;
	ScrollRect scrollRect;

	void Awake () {
		scrollRect = GetComponent<ScrollRect>();
		if (controllingScrollRect != null)
			controllingScrollRect.onValueChanged.AddListener(MirrorPos);
	}

	void MirrorPos(Vector2 scrollPos) {

		if (clamp) 
			scrollRect.normalizedPosition = new Vector2( Mathf.Clamp01(scrollPos.x), Mathf.Clamp01(scrollPos.y) );
		else
			scrollRect.normalizedPosition = scrollPos;
	}

}

Enjoy!

– Martin