# Movement appears to be frame rate dependent, despite use of Time.deltaTime

I have the following code to calculate the translation required to move a game object in Unity, which is called in LateUpdate. From what I understand, my use of Time.deltaTime should make the final translation frame rate independent (please note CollisionDetection.Move() is just performing raycasts).

public IMovementModel Move(IMovementModel model) {
this.model = model;

targetSpeed = (model.HorizontalInput + model.VerticalInput) * model.Speed;

model.CurrentSpeed = accelerateSpeed(model.CurrentSpeed, targetSpeed,
model.Accel);

if (model.IsJumping) {
model.AmountToMove = new Vector3(model.AmountToMove.x,
model.AmountToMove.y);
} else if (CollisionDetection.OnGround) {
model.AmountToMove = new Vector3(model.AmountToMove.x, 0);
}

model.FlipAnim = flipAnimation(targetSpeed);
// If we're ignoring gravity, then just use the vertical input.
// if it's 0, then we'll just float.
gravity = model.IgnoreGravity ? model.VerticalInput : 40f;

model.AmountToMove = new Vector3(model.CurrentSpeed, model.AmountToMove.y - gravity * Time.deltaTime);

model.FinalTransform =
CollisionDetection.Move(model.AmountToMove * Time.deltaTime,
model.BoxCollider.gameObject, model.IgnorePlayerLayer);
// Prevent the entity from moving too fast on the y-axis.
model.FinalTransform = new Vector3(model.FinalTransform.x,
Mathf.Clamp(model.FinalTransform.y, -1.0f, 1.0f),
model.FinalTransform.z);

return model;
}

private float accelerateSpeed(float currSpeed, float target, float accel) {
if (currSpeed == target) {
return currSpeed;
}
// Must currSpeed be increased or decreased to get closer to target
float dir = Mathf.Sign(target - currSpeed);
currSpeed += accel * Time.deltaTime * dir;
// If currSpeed has now passed Target then return Target, otherwise return currSpeed
return (dir == Mathf.Sign(target - currSpeed)) ? currSpeed : target;
}

private void OnMovementCalculated(IMovementModel model) {
transform.Translate(model.FinalTransform);
}


If I lock the game's framerate to 60FPS, my objects move as expected. However, if I unlock it (Application.targetFrameRate = -1;), some objects will move at a much slower rate then I would expect when achieving ~200FPS on a 144hz monitor. This only seems to happen in a standalone build, and not within the Unity editor.

GIF of object movement within the editor, unlocked FPS

http://gfycat.com/SmugAnnualFugu

GIF of object movement within the standalone build, unlocked FPS

http://gfycat.com/OldAmpleJuliabutterfly

Frame based simulations will experience errors when updates fail to compensate for non-linear rates of change.

For example consider an object starting with position and velocity values of zero experiencing a constant acceleration of one.

If we apply this update logic:

velocity += acceleration * elapsedTime
position += velocity * elapsedTime


We can expect these results under differing frame rates: The error is caused by treating the final velocity as though it applied for the entire frame. This is similar to a Right Riemann Sum and the amount of error varies with frame rate (illustrated on a different function): As MichaelS points out, this error will be halved when frame duration is halved, and may become inconsequential at high frame rates. On the other hand any games that experience performance spikes or long running frames may find this produces unpredictable behaviour.

Luckily kinematics allows us to accurately calculate the displacement caused by linear acceleration:

d =  vᵢ*t + (a*t²)/2

where:
d  = displacement
vᵢ = initial velocity
a  = acceleration
t  = elapsed time

breakdown:
vᵢ*t     = movement due to the initial velocity
(a*t²)/2 = change in movement due to acceleration throughout the frame


So if we apply this update logic:

position += (velocity * elapsedTime) + (acceleration * elapsedTime * elapsedTime / 2)
velocity += acceleration * elapsedTime


We will have the following results: • This is useful information, but how does it actually address the code in question? First, the error goes down dramatically as framerate increases, so the difference between 60 and 200 fps is negligible (8 fps vs infinity is already only 12.5% too high). Second, once the sprite is at full speed, the biggest difference is being 0.5 units ahead. It shouldn't affect the actual walking speed as shown in the .gifs attached. When they turn around, the acceleration is seemingly instant (possibly several frames at 60+ fps, but not full seconds). – MichaelS Feb 26 '16 at 9:32
• That's a Unity or code issue then, not a math issue. A quick spreadsheet says if we use a=1, vi=0, di=0, vmax=1, we should hit vmax at t=1, with d=0.5. Doing that over 5 frames (dt=0.2), d(t=1)=0.6. Over 50 frames (dt=0.02), d(t=1)=0.51. Over 500 frames (dt=0.002), d(t=1)=0.501. So 5 fps is 20% high, 50 fps is 2% high, and 500 fps is 0.2% high. In general, the error is 100/fps percent too high. 50 fps is about 1.8% higher than 500 fps. And that's just during acceleration. Once velocity hits max, there should be zero difference. With a=100 and vmax=5, there should be even less difference. – MichaelS Feb 26 '16 at 13:54
• In fact, I went ahead and used your code in a VB.net app (simulating dt of 1/60 and 1/200), and got Bounce: 5 at frame 626 (10.433) seconds vs. Bounce: 5 at frame 2081 (10.405) seconds. 0.27% more time at 60 fps. – MichaelS Feb 26 '16 at 13:57
• It's your "kinematic" approach that gives a 10% difference. The traditional approach is the 0.27% difference. You just labeled them incorrectly. I think it's because you're incorrectly including acceleration when velocity is maxed. Higher framerates add less error per frame, so give a more accurate result. You need if(velocity==vmax||velocity==-vmax){acceleration=0}. Then the error drops substantially, though it's not perfect since we don't figure out exactly what part of the frame acceleration ended. – MichaelS Feb 26 '16 at 14:59

It depends on where you're calling your step from. If you're calling it from Update, your movement will indeed be framerate independent if you scale with Time.deltaTime, but if you're calling it from FixedUpdate, you need to scale with Time.fixedDeltaTime. I figure you're calling your step from FixedUpdate, but scaling with Time.deltaTime, which would result in decreased apparent speed when Unity's fixed step is slower than the main loop, which is what's happening in your standalone build. When the fixed step is slow, fixedDeltaTime is large.

• It is being called from LateUpdate. I'll update my question to make that clear. Though I believe Time.deltaTime will still use the correct value regardless of where it's called (if used in FixedUpdate, it'll use fixedDeltaTime). – Cooper Feb 25 '16 at 14:30