Timesteps and Achieving Smooth Motion in Unity

One of the most intensely debated topics in the Unity community is how to go about removing jerky movement from games, and rightfully so. The issue is universal to all engines, and is directly derived from what timesteps your engine uses. There is no single solution that works for every situation, but there are certainly sub-optimal practices. Many developers have encountered the issue of motion stutter at one point or another, but getting help can prove difficult. There is a surprising amount of misinformation out there regarding timesteps in Unity. Many answers on the Unity forums, while correct, aren’t comprehensive and leave gaps in understanding needed to fully resolve the issue. This article aims to tackle the issue in more depth by explaining timesteps in Unity, where and why they can lead to stutter, and presenting a solution that helps resolve the issue. An asset package is provided, along with a demonstration of the solution.

Eww! Is this type of stutter familiar?

Eww! Is this type of stutter familiar?

The above image is a simple example of motion stutter in action. Clearly this is not behavior we want in our games. This is very easy to replicate yourself:

  1. Create a new project
  2. Import the default first person character controller and put it in a fresh scene
  3. Place some objects and circle one of them.

Removing the head bobbing and using a gamepad makes this effect easier to observe. In general, you should notice particularly bad stutter when moving while looking around.

Now look at this case, where I’ve applied the techniques we will discuss later. Comparing the first example to the second, and you should see a significant difference in the smoothness.

Nice and smooth. Much better!

Nice and smooth. Much better!

Prior to tackling the issue, it is important to understand Unity’s update cycle. In particular, we need to examine the logic behind the Update and FixedUpdate methods. Below is a small excerpt from Unity’s documentation on execution order found here: https://docs.unity3d.com/Manual/ExecutionOrder.html

In particular, you’ll want to examine the Update Order segment carefully if you have not before, as it is highly relevant for the remainder of this article.

Unity Update Order. Source: https://docs.unity3d.com/Manual/ExecutionOrder.html Get to know this flowchart!

Unity Update Order. Source: https://docs.unity3d.com/Manual/ExecutionOrder.html

Get to know this flowchart!

This chart outlines the order that methods are called during a single frame in Unity. The parts we will focus on are the FixedUpdate and Update methods, highlighted in green and red respectively.

Unity implements what is known as a semi-fixed timestep. That means the main game loop can run at any frame rate using a variable timestep, called deltaTime in Unity, and that manages an internal loop which uses fixed timesteps. This has some advantages; primarily, being able to run visual updates as fast as hardware permits, while locking the core game simulation to a constant rate. There are disadvantages though, including being prone to the aforementioned stutter. The following is a simplified representation of what Unity’s update loop is probably structured like:

float currentSimulationTime = 0;
float lastUpdateTime = 0;

while (!quit) // variable steps
{
        while (currentSimulationTime  < Time.time) // fixed steps
        {		
              FixedUpdate();
              Physics.SimulationStep(currentState, Time.fixedDeltaTime);
              currentSimulationTime += Time.fixedDeltaTime;
        }
 Time.deltaTime = Time.time - lastUpdateTime;
 Update();
 Render();
 lastUpdateTime = Time.time;
}

The Update method is likely familiar to you already. It is called on Monobehaviors every frame just after inputs are processed by Unity and before the screen is rendered. If VSync is disabled, when one frame is completed, Unity will immediately start on the next one, therefore attaining the highest possible framerate. Since hardware and the complexity of each frame varies, the frame rate is never constant. Even with VSync enabled, causing Unity to try to match a specific frame rate, it is not truly constant. Because of this, Update can be called any number of times per second.

FixedUpdate is called on Monobehaviors each time the physics simulation is progressed. Unity treats these calls as if they are a fixed time apart, even though in actual time multiple simulation steps are not calculated at even time intervals. This is done because game physics, in particular accelerated motion, is most accurate and stable given even delta times. That fixed timestep is known as fixedDeltaTime within Unity. By default it has a value of 0.02, meaning there are always 50 physics steps and FixedUpdate calls for every second of the game. In this way, one can think of FixedUpdate as frame rate independent as it is called the same number of times per second, even if the rendering frame rate is very low or high. It is necessary to stress that the FixedUpdate and physics loop is synchronous, and does not occur on a separate thread.


At this point, let’s observe some examples of update timings.

Above is what the timings might look with 50 FixedUpdates and 60 frames per second. However, this is not perfectly achievable since frame rate varies, so a more realistic, though exaggerated, timeline is below. Notice how some frames are close together with no physics steps between them, which typically occurs when there is low rendering complexity.  On the other hand, other updates have long pauses between them with numerous physics steps computed between each frame, which often happens when loading assets. This means that even if you try to match frame rate with the number of fixed steps, they are not guaranteed to be aligned.

Update Timings2.png

Using our knowledge of Unity’s timesteps, we can now understand the case presented below. In this scene, there is a sphere and camera both orbiting a pivot. The sphere’s transform is set in the Update loop, while the camera’s transform is set in the FixedUpdate loop on the left, and Update on the right. The left side has obvious stuttering, while the right remains smooth.

Camera moves in FixedUpdate on the left, Update on the right. 

Camera moves in FixedUpdate on the left, Update on the right. 

Because Update is called at a different rate than FixedUpdate, the sphere often moves while the camera remains still. This causes the sphere to move inconsistently relative to the camera, creating the stutter. Slowing things down, that behavior can be observed.

Same example as above, 5% speed.

Same example as above, 5% speed.

So, we can see the root cause of this stutter is moving some objects in Update and others in FixedUpdate. The simple fix is indeed to move all transforms in either Update or FixedUpdate.

However, this is where things get tricky. The common answer found among Unity developers is to put most game and movement logic in Update and use FixedUpdate only for the occasional physics task. While this has merits, including simplicity of use, it is problematic for many reasons. Perhaps most important is that your gameplay is frame rate dependent. This opens the door for plenty of gameplay affecting bugs and inconsistent behavior. Furthermore, it prevents determinism, which almost the entire real time strategy genre, for example, depends on. It also introduces problems when you need accelerated motion for an object, such as a character controller’s gravity. For those objects FixedUpdate should be used, but since other objects are moved in Update you’ll get stutter (see the standard assets first person character controller to observe this exact issue).

Therefore, a common and sometimes necessary alternative is to put all state and gameplay logic in a fixed timestep like FixedUpdate and strictly handle visuals and input logic in Update. This is not without its own challenges however. First off, you may want your physics steps to occur at a different rate from game logic ticks depending on your game. That can be resolved by implementing your own fixed timestep loop independent of FixedUpdate that ticks at whatever rate you wish. This is fairly simple to do and can give you a lot of control, allowing for fine tuned optimization. Next, inputs can be missed when read only in FixedUpdate, since multiple frames could occur between FixedUpdates, and only the last frame’s input survives to the following FixedUpdate. This particularly affects button up and down events, as they are only active for a single frame. The solution to this issue is buffering inputs by storing them each frame until they are all processed during the next FixedUpdate. Integrating this behaviour into whatever input controller you use is a fairly seamless way to do this and keeps the buffering in one place.

One larger issue, however, is that FixedUpdate is typically called less than the client frame rate, so moving objects don’t update their position as frequently as the screen is rendered. This makes the game somewhat choppy, though consistent. There are various ways to resolve this centering around using interpolation and extrapolation to fill the frames between FixedUpdates, smoothing the movement out. Interpolation, moving an object smoothly from one game state to the following state, is convenient in that it can be applied to most objects and work easily, but does introduce a fixedDeltaTime worth of latency. This latency is generally accepted however, and plenty of games, even twitch shooters and such, allow for this delay to gain smoothness. Extrapolation, predicting where an object will be next fixed step, avoids latency, but is inherently more difficult to get working seamlessly and comes with a performance cost.

Camera and sphere moving in FixedUpdate, using interpolation on the right side only.

Camera and sphere moving in FixedUpdate, using interpolation on the right side only.

Above is another comparison to demonstrate interpolation. On the left side, both the camera and sphere have their transform set in FixedUpdate. The right side is the same, but with interpolation moving the transforms smoothly between FixedUpdate steps. Notice how both objects remain aligned in either case, but on the right side the more frequent transform updates reduce stutter.

So, how might this interpolation be done within Unity? I’ve made an asset package containing a setup similar to what I’ve used in the past which you can get here.

Additionally, you can grab the build used to create the above examples here.

The setup works based on three scripts, found under the “Assets / Scripts / FixedInterpolation” directory after importing the package. Those scripts are fully commented, but more compact versions are provided here with a brief description below.

1. InterpolationController - Stores the timestamps of the two most recent fixed steps, and by comparing them against the time during Update generates a global interpolation factor. The script must be attached to a single gameobject in the scene.

using UnityEngine;
using System.Collections;

public class InterpolationController : MonoBehaviour
{
    private float[] m_lastFixedUpdateTimes;
    private int m_newTimeIndex;

    private static float m_interpolationFactor;
    public static float InterpolationFactor {
        get { return m_interpolationFactor; }
    }

    public void Start() {
        m_lastFixedUpdateTimes = new float[2];
        m_newTimeIndex = 0;
    }

    public void FixedUpdate()  {
        m_newTimeIndex = OldTimeIndex();
        m_lastFixedUpdateTimes[m_newTimeIndex] = Time.fixedTime;
    }

    public void Update() {
        float newerTime = m_lastFixedUpdateTimes[m_newTimeIndex];
        float olderTime = m_lastFixedUpdateTimes[OldTimeIndex()];

        if (newerTime != olderTime) {
            m_interpolationFactor = (Time.time - newerTime) / (newerTime - olderTime);
        } else {
            m_interpolationFactor = 1;
        }
    }
    
    private int OldTimeIndex() {
        return (m_newTimeIndex == 0 ? 1 : 0);
    }
}

2. InterpolatedTransform - Stores the transforms for an object after the two most recent fixed steps, and interpolates the object between them using the global interpolation factor. It also ensures that the object is placed back where it was last fixed step before the current fixed step executes, instead of where it was interpolated to last. This means that any scripts moving the transform are working from the correct state. If you teleport an object and want to prevent interpolation, call the ForgetPreviousTransforms method after moving the object. This script should be attached to any objects moved during a FixedUpdate.

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(InterpolatedTransformUpdater))]
public class InterpolatedTransform : MonoBehaviour
{
    private TransformData[] m_lastTransforms;
    private int m_newTransformIndex;

    void OnEnable() {
        ForgetPreviousTransforms();
    }

    public void ForgetPreviousTransforms() {
        m_lastTransforms = new TransformData[2];
        TransformData t = new TransformData(
                                transform.localPosition,
                                transform.localRotation,
                                transform.localScale);
        m_lastTransforms[0] = t;
        m_lastTransforms[1] = t;
        m_newTransformIndex = 0;
    }

    void FixedUpdate() {
        TransformData newestTransform = m_lastTransforms[m_newTransformIndex];
        transform.localPosition = newestTransform.position;
        transform.localRotation = newestTransform.rotation;
        transform.localScale = newestTransform.scale;
    }

    public void LateFixedUpdate() {
        m_newTransformIndex = OldTransformIndex();
        m_lastTransforms[m_newTransformIndex] = new TransformData(
                                                    transform.localPosition,
                                                    transform.localRotation,
                                                    transform.localScale);
    }

    void Update() {
        TransformData newestTransform = m_lastTransforms[m_newTransformIndex];
        TransformData olderTransform = m_lastTransforms[OldTransformIndex()];

        transform.localPosition = Vector3.Lerp(
                                    olderTransform.position, 
                                    newestTransform.position, 
                                    InterpolationController.InterpolationFactor);
        transform.localRotation = Quaternion.Slerp(
                                    olderTransform.rotation, 
                                    newestTransform.rotation, 
                                    InterpolationController.InterpolationFactor);
        transform.localScale = Vector3.Lerp(
                                    olderTransform.scale, 
                                    newestTransform.scale,
                                    InterpolationController.InterpolationFactor);
    }

    private int OldTransformIndex() {
        return (m_newTransformIndex == 0 ? 1 : 0);
    }

    private struct TransformData {
        public Vector3 position;
        public Quaternion rotation;
        public Vector3 scale;

        public TransformData(Vector3 position, Quaternion rotation, Vector3 scale) {
            this.position = position;
            this.rotation = rotation;
            this.scale = scale;
        }
    }
}

3. InterpolatedTransformUpdater - Used to call a couple methods in InterpolatedTransform both before and after other script’s FixedUpdates. Must be placed on objects that also have InterpolatedTransform attached.

using UnityEngine;
using System.Collections;

public class InterpolatedTransformUpdater : MonoBehaviour
{
    private InterpolatedTransform m_interpolatedTransform;
    
    void Awake() {
        m_interpolatedTransform = GetComponent<InterpolatedTransform>();
    }
	
 void FixedUpdate() {
        m_interpolatedTransform.LateFixedUpdate();
    }
}
The required execution order.

The required execution order.

For these scripts to work, the execution order must be specified as above. As well, any objects with InterpolatedTransform attached must only be moved in a FixedUpdate, as any transformations made in Update will override the interpolation. Additionally, you should make sure that you buffer any inputs where necessary. While there are numerous ways you can do this, a good solution is to build input buffering into your own input controller, reducing complexity elsewhere. Finally, keep in mind that while this simple setup is surprisingly functional, there are many improvements that could be made for a production ready system, such as extrapolation.

In conclusion, put all your game logic in either Update or FixedUpdate. Do not mix and match timesteps unless you are willing to bite the bullet and accept some stutter. Additionally, it is strongly worth considering putting all game state in FixedUpdate, using Update exclusively for user input, visual effects, and interpolation between game states. While this requires a change how you structure your games, it is a proven design structure with many advantages.

To learn more about what we are doing to help developers build better games, faster - check out our multi-user scene collaboration tool for Unity, Scene Fusion.

Recommended Reading

More on variable, fixed, and semi-fixed timesteps: 

A case where placing all game state information in fixed timesteps only is necessary:

Unity docs:

 

Written by Scott Sewell, developer at KinematicSoup