Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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

share|improve this question
2  
You should give this a read. Time bucketing is what you want, and fixed time steps! gafferongames.com/game-physics/fix-your-timestep – Alan Wolfe Feb 25 at 17:18

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: enter image description here

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:

enter image description here

share|improve this answer
2  
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 at 9:32
2  
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 at 13:54
2  
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 at 13:57
2  
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 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.

share|improve this answer
1  
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). – Neeko Feb 25 at 14:30

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.