- Home /
Predict Where ScrollRect Will Land Based on Velocity
Hello! I know this may not be a normal question but I think you can help me figure it out.
Background: I want to create a scrollrect that snaps onto the elements it's scrolling. So that it always comes to a stop with an element in the center. The scrolling of these scroll rects is based on the velocity your finger was swiping at when it left the screen, and if you input a certain velocity it always moves the same amount (within 1 pixel).
So I figured the smoothest way to create this snapping scroll rect would be to predict where the scroll rect would land & then adjust the deceleration rate so that it landed on the nearest element instead.
So basically I would like to:
Turn this loop into a math function where I can input velocity & get out the movement delta.
Be able to figure out what the deceleration rate should be based on the end movement delta & velocity.
Here's the code that the scroll rect uses for its movement:
protected virtual void LateUpdate()
{
//It's probably easiest if you imagine positionX always starting at 0
//but I'm no expert
m_VelocityX *= Mathf.Pow(m_DecelerationRate, Time.unscaledDeltaTime);
if (Mathf.Abs(m_VelocityX) < 1)
{
m_VelocityX = 0;
}
positionX += m_VelocityX * Time.unscaledDeltaTime;
}
Where positionX is the x position of the ScrollRect's content. (it holds the UI elements I want to snap too) ScrollRect Code
And here are some velocities and movement deltas (how far it traveled) where m_DecelerationRate is 0.135 if that's helpful:
Velocity 500 -> 490
Velocity 350 -> 343
Velocity 200 -> 195
Velocity -200 -> -195
Velocity -400 -> -391
Ty all so much for the help! This math is way to hard for me to wrap my head around but I think it will end up being cool!
I'm working on a tutorial for this type of thing. I'll post a reply sometime in February.
Answer by cbauman · Feb 15, 2018 at 02:33 AM
Notation Reference:
v0 = initial velocity
vf = final velocity
r = deceleration rate
s(t) = distance/movementDelta at time, t (I know these two terms aren't interchangeable because distance can't be negative, but I already used it this way for my tutorial so...bare with me)
v0 is the velocity when OnPointerUp is called and vf is 1 because that's when Unity manually sets the velocity to 0.
To figure out scroll duration, you can just use the velocity equation (vf = v0 * r^t) to solve for t, since we already know v0, vf, and r.
movementDelta is a little more involved. It starts with one line of calculus. Yes, I know. A little intimidating, but after that it's back to basic algebra. Calculating the distance traveled over time, t is equal to the "area under the curve" of the velocity-time graph. Taking the integral of the velocity equation (v0 * r^t) helps us determine what that area is.
Helpful links if you're not comfortable with calculus:
Video: Why distance is area under velocity-time line
Video: Motion problems with integrals
Integral Cheat Sheet (we use the last equation under "Integrals involving only exponential functions")
"1. Turn this loop into a math function where I can input velocity & get out the movement delta."
public float CalculateMovementDelta(float v0, float vf, float r)
{
float movementDelta = (vf - v0) / Mathf.Log(r);
return movementDelta;
}
You also have to modify the position update to use the average velocity since the last frame, instead of the end of frame velocity. Otherwise, the ScrollRect code will undershoot the calculated movementDelta every time. The following illustration shows why.
As you can see, the bigger deltaTime is, the bigger the error. The last graph still isn't perfect, but it does a much better job of balancing the under- and over-shooting of total area (error of only about 0.2 with an initial velocity of 4000, in one of my tests). So, I would modify your LateUpdate function to look like this:
protected virtual void LateUpdate()
{
float prevVelocity = m_VelocityX;
m_VelocityX *= Mathf.Pow(m_DecelerationRate, Time.unscaledDeltaTime);
// avgVelocity = low + (high - low) / 2
float avgVelocity = m_VelocityX + (prevVelocity - m_VelocityX) / 2;
if (Mathf.Abs(m_VelocityX) < 1)
{
m_VelocityX = 0;
}
positionX += avgVelocity * Time.unscaledDeltaTime;
}
"2. Be able to figure out what the deceleration rate should be based on the end movement delta & velocity."
You just need to adjust the calculated movementDelta to compensate for the nearest element, and send that adjusted movementDelta through the following method to get the required deceleration rate:
public float CalculateDecelerationRate(float v0, float vf, float movementDelta)
{
float decelerationRate = Mathf.Exp((vf - v0) / movementDelta);
return decelerationRate;
}
Links
Here's the 3-part video tutorial I recently finished and based this post on:
And these two articles were extremely helpful to me:
Your answer
Follow this Question
Related Questions
Problems with Input.acceleration in Unity 4.1 1 Answer
Shooting works with PC Keyboard, but not Mobile Touch Controls?! 1 Answer
Input in OnTriggerStay2D 1 Answer
How to successfully apply force calling a function from another script 1 Answer
Raycast from touch, return if hit a specific object. 0 Answers