- Home /
can someone explain how using Time.deltaTime as 't' in a lerp actually works?
I can't quite get why using it gives the desired results.. and then at other times using Time.time works (eg in the lerp scripting reference examples time.time is used?)
Thanks...
cross-reference http://answers.unity3d.com/questions/9757/timetime-explanation.html Greetz, $$anonymous$$y.
I've written a blog-post to answer this exact question: http://www.blueraja.com/blog/404/how-to-use-unity-3ds-linear-interpolation-vector3-lerp-correctly
Answer by Eric5h5 · Apr 02, 2010 at 10:53 AM
Time.deltaTime is the time since the last frame. In Lerp, the third parameter is a float between 0 and 1. If it's 0, you get the first parameter in Lerp. If it's 1, you get the second. Therefore, with a statement like
transform.position = Vector3.Lerp(transform.position, target.position, Time.deltaTime);
in Update, you get a position that's part-way between transform.position
and target.position
, depending on the time difference between the current frame and the last frame.
For example, a Time.deltaTime value of .01 (100fps) would return a value that's a combo of 99% transform.position
and 1% target.position
. As this is repeated each frame, the transform.position
, assuming target.position
is stationary, becomes closer and closer to target.position
but never quite reaches it. (Unless the framerate drops to 1fps or less, in which case Time.deltaTime would be 1 or greater, so the return value would be 100% target.position
.)
Note that this is a "trick" usage of Lerp, which creates a "slow down as you approach the target" effect. It's mathematically incorrect, though, since it will produce results that vary somewhat depending on the framerate. This is better:
function MoveObject (thisTransform : Transform, startPos : Vector3, endPos : Vector3, time : float) : IEnumerator {
var i = 0.0;
var rate = 1.0/time;
while (i < 1.0) {
i += Time.deltaTime * rate;
thisTransform.position = Vector3.Lerp(startPos, endPos, i);
yield;
}
}
That's a coroutine that moves a transform from startPos
to endPos
over a given time. This happens as i
advances from 0.0 to 1.0, and results in completely linear movement, without the "slowdown" effect.
As for Time.time, consider
transform.position = Vector3.Lerp(startPos, endPos, Time.time);
in Update. This moves the transform from startPos
to endPos
as the time advances from 0.0 to 1.0. Once the time is past one second, no further movement will occur since the third parameter in Lerp is clamped between 0 and 1. Obviously this isn't very useful unless you actually only want movement to happen for the first second of gameplay for some reason.
One issue I noticed with the "trick" example, is that the object will visually appear to have reached it's destination, but the while loop is still counting down behind the scenes for a few more milliseconds. This means if you're waiting for the while loop to finish before firing off something else, there's a perceived "lag" from the time your eye sees the object stop until the next thing is triggered. To fix this, I had to add: if(i >= 0.8) break; to break the while loop at the point where I visually see the object has reached its endPos. Is there a better way to do this?
If you want a better way to achieve nearly the same effect as the "trick" example, but have a better way of knowing how long it takes is to just change your lerp to an easing lerp. A decent example is $$anonymous$$athfx.Sinerp, found here: http://www.unifycommunity.com/wiki/index.php?title=$$anonymous$$athfx
You treat it like a normal lerp, but it eases out at the end.
hi...you say "var rate = 1.0/time;"...if i was not doing this in a function what would time be? just Time.deltaTime?
If you want something that just runs e.g. every update ins$$anonymous$$d of in a coroutine, you could do something like:
float change = Time.deltaTime*speed;
if (curVal < targetVal) {
curVal += change;
if (curVal > targetVal) curVal = targetVal;
}
else {
curVal -= change;
if (curVal < targetVal) curVal = targetVal;
}
return curVal;
Note that "speed" here is different to "time" above, in that it doesn't refer to any specific amount of time to transition between values.
Answer by Ippokratis · Feb 05, 2011 at 10:30 AM
Another script based on Eric's, that prevents the jag effect. Thanks to Eric for the original script, the idea of rate is very usefull.
var pointA:Vector3; var pointB:Vector3; var time:float = 10.0; private var i:float = 0.0; private var rate:float = 0.0;
function Update () {
MoveObject(this.transform, pointA, pointB, time); }
function MoveObject (thisTransform : Transform, startPos : Vector3, endPos : Vector3, time : float) { rate = 1.0/time; if (i < 1.0) { i += Time.deltaTime * rate; thisTransform.position = Vector3.Lerp(startPos, endPos, i); } }
A problem with this script is that $$anonymous$$oveObject can only be used properly for one object at a time, since "i" and "rate" aren't local to the coroutine.
Answer by thecommentorlord · Jul 20, 2020 at 10:03 AM
I have a potentially simpler solution to that of Eric’s involving the “slow down while approaching target” effect (the curvature is slightly different though):
curPos = Vector3.Lerp(curPos, endPos, 1-Pow(2, -Time.deltaTime * rate));
curPos = current position
endPos = end position
2 = arbitrary, could be anything larger than 1 (changing it does the same thing as changing the rate).
rate = the rate of the movement - higher value means approaching the end position faster.
Here’s an example to prove this works:
For simplicity let’s just assume the positions are real numbers.
curPos = 1 (at the beginning), endPos = 0, rate = 1.
We’ll track curPos over the duration of 1 time unit to determine the final result in two cases:
1. If Time.deltaTime = 1:
in this case the calculation will run 1 time:
curPos = Lerp(1, 0, 1-Pow(2, -1*1)) = Lerp(1, 0, 0.5) = 0.5;
so the final result is 0.5.
2. If Time.deltaTime = 0.25:
in this case the calculation will run 4 times:
curPos = Lerp(1, 0, 1-Pow(2, -0.25*1)) = Lerp(1, 0, 0.16) = 0.84;
curPos = Lerp(0.84, 0, 1-Pow(2, -0.25*1)) = Lerp(0.84, 0, 0.16) = 0.705;
curPos = Lerp(0.705, 0, 1-Pow(2, -0.25*1)) = Lerp(0.705, 0, 0.16) = 0.59;
curPos = Lerp(0.59, 0, 1-Pow(2, -0.25*1)) = Lerp(0.59, 0, 0.16) = 0.5;
so the final result is still 0.5, regardless of Time.deltaTime’s value.
The reason this works is actually pretty simple.
Going with the previous example, what we actually did was this:
1. If Time.deltaTime = 1: curPos = 1*2^(-1)
2. If Time.deltaTime = 0.25: curPos = 1*2^(-0.25) *2^(-0.25) *2^(-0.25) *2^(-0.25) = 1*2^(0.25-0.25-0.25-0.25) = 1*2^(-1)
I hope this can help someone.
Sorry but your math is totally broken ^^. You take 2 to a negative power so it's the same as doing one over 2 to the positive power. At the moment when deltaTime gets larger it means the movement should get faster and when deltaTime gets smaller it should get slower. However your code does the opposite.
Your assumption about Lerp is the wrong way around as well. This is not true:
curPos = Lerp(1, 0, Pow(2, -0.25*1)) = Lerp(1, 0, 0.84) = 0.84;
This line does not result in 0.84 but in 0.16 since you move 84% of the remaining distance towards the target. So from "1" distance only 0.16 are left over.
Besides that all your results are wrong, how is that simpler? The Pow function with fractional exponents is one of the most expensive floating point operations. What you probably had in $$anonymous$$d was using your calculated factor as multiplier. Though that's not what is happening here.
When you post a new solution to a 10 years old question it has to be somewhat groundbreaking ^^ At least run some tests inside the actual software with some extreme cases.
@Bunny83 you're right, I got the Lerp backwards. I'm used to multipliers. Fixed it now. The idea of this solution is that it's a mathematically correct method that can be written in one line. Not the most efficient method, but most of the time it will suffice. I'm sorry this isn't groundbreaking, it's just a useful method I came up with, and I wanted to put it out there in case somebody finds it helpful. Frankly I don't think I will be doing that again if this is the reaction I'm getting.
I think this approach is the only technically correct solution in order to deltaTime inside Lerp or in general to use Lerp for dead reckoning with a variable frame rate. I'll check the math again, though, because we came up with a slightly different expression.
Answer by cregox · Nov 22, 2013 at 10:21 PM
Eric mentioned the FPS can vary and it will throw unexpected high values for deltaTime
. I think this deserves more attention.
(1)
In practice, when I was using Time.deltaTime
and Quaternion.Lerp (it's actually irrelevant if it's quaternion or vector3 or whatever lerp) I was getting this nasty looking error:
CompareApproximately (aScalar, 0.0F)
UnityEngine.Quaternion:Lerp(Quaternion, Quaternion, Single)
This is because, eventually the deltaTime would make it bigger than 1, thus clamping to 1 and, in my code, for the 1st frame only, the destination rotation was a very bad (0, 0, 0, 0);
. The point here is: both the clamping and the bad value were unexpected, resulting into a little hard to debug error. Mostly because it's hard to reproduce.
The true effect was nothing that makes the lerping noticeable. But this is something to consider for point 2:
(2)
As we can see in (1) there is this extra problem with the trick as it will not behave the same every time, precisely because the FPS can vary. But it's not that easy to reproduce the trick also, because it kind of balances out processor speeds and so it's one of the best tricks to make a Lerp work similarly in different machines.
For instance, a much simpler way to achieve the same ease out trick effect is to just set a const
:
transform.rotation = Quaternion.Lerp(originalRotation, targetRotation, 0.1f);
But if you run that on a i7 quad core versus on an iPhone, you'll already see the difference.
tl;dr
So, there is in fact a good use for Time.detalTime
on lerping. You just need to be aware of the caveats!
Answer by astracat111 · Feb 28, 2018 at 01:19 AM
Hopefully someone reads this part as well because this took me some testing to figure out since I"m such a noob...
If you move an gameObject over frames like this:
currentDistance = (currentFrame / target_Frame);
gameObject_ToMove.transform.position = Vector3.Lerp (startingPosition, targetPosition, currentDistance);
To make it frame independent it's incredibly simple. You just want to add Time.deltaTime onto the end of the current distance like so:
currentDistance = (currentFrame / target_Frame) + (0.01f * Time.deltaTime);
gameObject_ToMove.transform.position = Vector3.Lerp (startingPosition, targetPosition, currentDistance);
The reason I use '0.01f' is because if you just add Time.deltaTime onto the end of your equation alone it will go too fast.
You shouldn't hardcode that figure at all, but ins$$anonymous$$d make it a variable. It's then a Scale factor.