Skip to content

Instantly share code, notes, and snippets.

@russianryebread
Last active June 25, 2023 04:27
Show Gist options
  • Save russianryebread/46dc971afe20861e9158 to your computer and use it in GitHub Desktop.
Save russianryebread/46dc971afe20861e9158 to your computer and use it in GitHub Desktop.
Inertial Scrolling for Unity 3D

Inertial Scrolling for Unity 3D

A simple C# script to port the inertial scrolling found in native iOS UIScrollViews to Unity. Requires HOTween for the 'elastic' bounces on the end.

Usage

Usage is quite simple. Attach the script as a component to the GameObject that you would like to be scrolled with inertia. Set the height of the view, and the height of the content, and it should do the rest.

Sample Code

using UnityEngine;
using System.Collections;
using  FS.Handlers;

private bool scrolling = false;
private FSScrollController scrollController;

public class ExampleClass : MonoBehaviour {
    void Awake() {
      scrollController = transform.Find("ITEM_TO_SCROLL").gameObject.AddComponent<FSScrollController>();
    }
    
    void Update() {
			scrolling = scroller.ScrollController();
			
			// The above method returns a bool, which can be
			// used to determine whether or not to allow clicking:
			//if(!scrolling) clickHandler();
    }
}
using UnityEngine;
using System.Collections;
using Holoville.HOTween;
namespace FS.Handlers
{
public class FSScrollController : MonoBehaviour {
public float contentOverflowY = 0f;
public float contentHeight = 0f;
public float viewHeight = 0f;
public float inertiaDuration = 0.95f;
private Vector3 scrollPosition;
private float scrollVelocity = 0f;
private float timeTouchPhaseEnded = 0f;
private float elasticSpringBackTime = 0.5f;
static private float SCROLL_RESISTANCE_COEFFICIENT = 8f;
private void setupBounds(){
// create a default viewHeight, unless it is provided for us externally.
if (viewHeight == 0f) {
viewHeight = (Camera.main.orthographicSize * 2);
}
// create a default contentHeight, unless it is provided for us externally.
if (contentHeight == 0f) {
contentHeight = viewHeight;
}
// contentOverflowY is calculated by subtracting the viewheight from the content height.
// this leaves us with the amount we need to be able to move the view.
if (contentOverflowY == 0f) {
contentOverflowY = contentHeight - viewHeight;
}
// Finally, if our overflow is less than the viewHeight, we get strange scrolling
// behavior, since it snaps the bottom of the content to the bottom of the view.
if (contentOverflowY < 0) {
contentOverflowY = 0;
}
}
public void Reset() {
// create a default viewHeight, unless it is provided for us externally.
viewHeight = 0f;
contentHeight = 0f;
contentOverflowY = 0f;
}
public bool ScrollController(){
bool scrolling = false;
setupBounds();
scrollPosition = this.gameObject.transform.position;
// Support the computer scrollwheel
float scroll = Input.GetAxis("Mouse ScrollWheel");
if (0f != scroll) {
scrollPosition = this.gameObject.transform.position;
scrollPosition += Vector3.down * scroll * 400f;
this.gameObject.transform.position = scrollPosition;
SpringToEdge();
// We are scrolling with the mouse scrollwheel,
// so we don't need to execute any more code here.
return true;
}
if (Input.touchCount < 1) {
if ( scrollVelocity != 0.0f ) {
// slow down over time
float t = (Time.time - timeTouchPhaseEnded) / inertiaDuration;
float frameVelocity = Mathf.Lerp(scrollVelocity, 0f, t);
scrollPosition.y += (frameVelocity * Time.deltaTime);
// We finish after the time is up.
if (t >= inertiaDuration) scrollVelocity = 0.0f;
SpringToEdge();
this.gameObject.transform.position = scrollPosition;
}
return false;
}
Touch touch = Input.touches[0];
float drag;
if (touch.phase == TouchPhase.Began){
scrollVelocity = 0.0f;
scrolling = true;
} else if (touch.phase == TouchPhase.Moved) {
float velocity;
// Figure out where we are starting from:
drag = scrollPosition.y;
// Add scroll resistance if we are past the bounds of the screen
// AKA elastic scrolling - doesn't work quite right.
// With micro-movements of the fingers, we get to a place where we
// start working our way back to the start.
if(scrollPosition.y < 0)
drag -= drag/SCROLL_RESISTANCE_COEFFICIENT;
else if(scrollPosition.y > contentOverflowY)
drag -= SCROLL_RESISTANCE_COEFFICIENT;
drag += touch.deltaPosition.y;
scrollPosition.y = drag;
// Impart momentum to the scrolled view, using the velocity of the finger
// to compute the momentum of the scroll.
velocity = (touch.deltaPosition.y / touch.deltaTime);
// Moving average filter to get rid of wild variations...
scrollVelocity = 0.8f * velocity + 0.2f * scrollVelocity;
// If we move past a certain threshold, then we are scrolling
// otherwise, we are possibly trying to tap. This allows us
// to detect when we are just trying to push a button.
scrolling = (Mathf.Abs(touch.deltaPosition.y) >= 3.0f) ? true : false;
} else if (touch.phase == TouchPhase.Ended) {
// track the time we ended so we can compute when to stop the scroll.
timeTouchPhaseEnded = Time.time;
// Add the elastic bounce to the end if we're past bounds.
SpringToEdge();
scrolling = false;
}
this.gameObject.transform.position = scrollPosition;
return scrolling;
}
private void SpringToEdge() {
TweenParms parms = new TweenParms();
if(scrollPosition.y < 0) {
scrollVelocity = 0f;
parms.Prop("position", new Vector3(scrollPosition.x, 0, scrollPosition.z)); // Position tween
parms.Ease(EaseType.EaseOutCubic); // Easing type
HOTween.To(this.gameObject.transform, elasticSpringBackTime, parms);
return;
}
if(scrollPosition.y > contentOverflowY) {
scrollVelocity = 0f;
parms.Prop("position", new Vector3(scrollPosition.x, contentOverflowY,scrollPosition.z)); // Position tween
parms.Ease(EaseType.EaseOutCubic); // Easing type
HOTween.To(this.gameObject.transform, elasticSpringBackTime, parms);
return;
}
}
}
}
@russianryebread
Copy link
Author

I do not, unfortunately. I know that the guy who took over the project after me made some major improvements to this code, but I don't think he open sourced those changes.

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