Motion Interpolation Solution (to eliminate FixedUpdate() stutter)

This post is useful for those who are interested in a custom solution for motion-interpolation in Unity. If you are new to the topic of fixed timestep loops and motion-interpolation, here is an introduction to that:
https://gafferongames.com/post/fix_your_timestep/

And here is the complementary thread that shows how to make fixed timestep logic more responsive and potentially even more performant:
https://discussions.unity.com/t/940117

And this here documents how FixedUpdate() is timed in Unity:
https://docs.unity3d.com/Manual/TimeFrameManagement.html

The goal is to achieve stutter-free visuals for deterministic game logic that runs in FixedUpdate(). This
TransformInterpolator script does just that:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// How to use TransformInterpolator properly:
/// 0. Make sure the gameobject executes its mechanics (transform-manipulations)
/// in FixedUpdate().
/// 1. Make sure VSYNC is enabled.
/// 2. Set the execution order for this script BEFORE all the other scripts
/// that execute mechanics.
/// 3. Attach (and enable) this component to every gameobject that you want to interpolate
/// (including the camera).
/// </summary>
public class TransformInterpolator : MonoBehaviour
{
   private struct TransformData
   {
       public Vector3    position;
       public Vector3    scale;
       public Quaternion rotation;
   }

//Init prevTransformData to interpolate from the correct state in the first frame the interpolation becomes active. This can occur when the object is spawned/instantiated.
   void OnEnable()     
    {
        prevTransformData.position = transform.localPosition;
        prevTransformData.rotation = transform.localRotation;
        prevTransformData.scale    = transform.localScale;
        isTransformInterpolated    = false;
    }

   void FixedUpdate()
   {
      //Reset transform to its supposed current state just once after each Update/Drawing.
       if (isTransformInterpolated)
       {
           transform.localPosition = transformData.position;
           transform.localRotation = transformData.rotation;
           transform.localScale    = transformData.scale;

           isTransformInterpolated = false;
       }

       //Cache current transform state as previous
       //(becomes "previous" by the next transform-manipulation
       //in FixedUpdate() of another component).
       prevTransformData.position = transform.localPosition;
       prevTransformData.rotation = transform.localRotation;
       prevTransformData.scale    = transform.localScale;
   }

   void LateUpdate()   //Interpolate in Update() or LateUpdate().
   {
       //Cache the updated transform so that it can be restored in
       //FixedUpdate() after drawing.
       if (!isTransformInterpolated)
       {
           transformData.position = transform.localPosition;
           transformData.rotation = transform.localRotation;
           transformData.scale    = transform.localScale;

           //This promise matches the execution that follows after that.
           isTransformInterpolated = true;
       }

       //(Time.time - Time.fixedTime) is the "unprocessed" time according to documentation.
       float interpolationAlpha = (Time.time - Time.fixedTime) / Time.fixedDeltaTime;

       //Interpolate transform:
       transform.localPosition = Vector3.Lerp(prevTransformData.position,
       transformData.position, interpolationAlpha);
       transform.localRotation = Quaternion.Slerp(prevTransformData.rotation,
       transformData.rotation, interpolationAlpha);
       transform.localScale = Vector3.Lerp(prevTransformData.scale,
       transformData.scale, interpolationAlpha);
   }

   private TransformData transformData;
   private TransformData prevTransformData;
   private bool isTransformInterpolated;
}

The alternative to custom motion-interpolation is Rigidbody.interpolation, that is natively provided by Unity. Here are the pros and cons of custom motion-interpolation so that you can make your decision.

Custom motion-interpolation pros:

  • You can turn off the TransformInterpolator for offscreen objects to achieve significantly better performance than doing so by using Rigidbody.
  • Transform is in sync with its state (not so otherwise: changes to Transform and Rigidbody.position are not immediate when moved by RigidBody.MovePosition()).

Custom motion-interpolation cons:

  • Less performant than Rigidbody.interpolation in general

Performance tips:

  • Don't interpolate Transform.localScale if it remains constant, as most objects don't grow in size. You might gain about 10% performance just by doing that if you have many objects on the screen. Engine calls can add up and get expensive.

  • Turn off interpolation for offscreen objects.

  • If the processing budget is too tight, it can still be Ok to just interpolate the camera and the main character/s. Running the TransformInterpolator for few objects won't be the bottleneck.

4 Likes

You're over looking an important point: Fixed update should be used for physics only, nothing more.

If you want your own loop that runs at a fixed interval, introduce your own manager.

3 Likes

What would be the benefit of your own manager?

[quote=“RetroMasters82”, post:3, topic: 891926]
What would be the benefit of your own manager?
[/quote]
Well… you can make it do whatever you want in whatever manner you want.

FixedUpdate works to the beat of the physics engine. It usually happens at your fixed timestep, but can happen at different rates depending on what’s happening. It should not be used for stuff that happens every n amount of time.

FixedUpdate() works to the beat of its scheduler (the physics engine just runs in it). A custom implementation doesn't change that principle (you cannot scatter updates equidistant in time in a single threaded architecture here), except for the amount of "fixed updates" to execute in a given frame. Here is where stability can be improved. But this is just an implementation detail that doesn't affect the intended use case of FixedUpdate(), that is running logic in fixed time steps.

The only thing I see is this: The update-local events have to be mapped/wrapped to work in FixedUpdate() in general, if you don't want to miss them (like GetKeyDown). But this is not a big deal to implement that. Other than that, FixedUpdate() is adequate to be used for any logic that you want to be deterministic.

[quote=“RetroMasters82”, post:5, topic: 891926]
But this is just an implementation detail that doesn’t affect the intended use case of FixedUpdate(), that is running logic in fixed time steps.
[/quote]
False!!!

FixedUpdate == Physics stuff only. I’ve seen this in writing by many of Unity’s devs here.

Fixed update may run at your specified time step but there are situations where it doesn’t and your intention to use it to be ‘deterministic’ will fall apart.

The intention of a fixed update loop is still to run logic in fixed time steps. Again, it doesn't mean that they are perfectly distributed on the timeline. They aren't, as calls to fixed update happen in bursts at the beginning of a frame. This imperfection is actually a requirement for the interpolation to work correctly since processing logic has to be finished before rendering the frame.:)

The claim about deterministic part is also misguided. The distribution of fixed updates in time doesn't affect determinism. It doesn't matter how you schedule to execute the same logic, the result will be the same.

I don't quite understand the point of the video, or what you're solving. You're not supposed to move objects in FixedUpdate, you allow the physics engine to move it for you, and I'm pretty sure it already interpolates that movement for your frame rate. The motions look jittery because it's not supposed to be moved there, it wasn't designed like that. Is there something that I'm missing?

2 Likes

If you are relying on physics, then this topic is not for you. This is for games which rely on custom mechanics that you want to be deterministic (so running in Update() and scaling movement with deltaTime is out of the question), as pointed out in the beginning. Yes, you could roll your own manager, as spiney suggested, but I would actually like to utilize Unity's architecture instead of working against it. So this is why running the logic in FixedUpdate() can be a sensible thing to do.

So, assuming you want to run the logic in FixedUpdate(), how can you then make it look smooth? This is what the TransformInterpolator is designed to solve. You can get an introduction to this topic here: https://gafferongames.com/post/fix_your_timestep/

[quote=“RetroMasters82”, post:9, topic: 891926]
Yes, you could roll your own manager, as spiney suggested, but I would actually like to utilize Unity’s architecture instead of working against it.
[/quote]
What do you mean?

Whenever I’ve used my own managers, they just run in Update. If I need to spread out the logic then I just use a simple timer, that, would you believe, also runs in Update.

Then it’s trivial to make a system for objects to subscribe to this manager.

You manually update components. Your code calls the update functions, right?

Could you give an example of some mechanic you wanted to be deterministic? The leading reason you want games to be deterministic is so that if you give the exact same inputs, the game would result in the same outcome, but the problem is that by desyncing your game from the Update loop, you cannot make that happen, as the game does not correctly receive input outside of Update.

[quote=“RetroMasters82”, post:11, topic: 891926]
You manually update components. Your code calls the update functions, right?
[/quote]
Well, more so I just introduce my own Update equivalent, usually defined by an interface. The manager just gets ticked by Update(), and calls this sub-Update call on everything subscribed to it.

Yes, this is what I mean by "working against the engine". Unity has an underlying framework that exposes and calls functions you are meant to just implement. You are sidestepping this infrastructure and roll your own manager to handle the gamelogic. In short, you are sidestepping Unity's flow of doing things. It might still work well for your particular needs, but this is something that is not done by default, but by necessity.

From my current Unity knowledge, I only see a strong reason to sidestep Unity's flow when you need more control over the order of execution than being able to change the script execution order is offering you.

Actually no. This is a misleading way to look at it because input is an external influence, outside of game's control. You don't expect the input to be deterministic, but the mechanical response to a received input. The mechanical possibility space is expected to remain invariant. So there are two major points why you want this determinism.

  1. Reproduceable edge cases the player can rely on
  2. Consistency in the result of accumulative mechanics (like walking from place A to B)

For the following examples, let's assume vsync is enabled so that framerate is synced with monitor's refresh rate (it still won't change the problem when it is off), and we are running the game in a variable time step loop.

An example for point 1. can be an object like a box. And the player discovered that he has just the right jump height to jump on it. The player expects this action to be reproduceable, certainly not dependent on monitor's refresh rate, and the noise in deltaTime.

The same is true for point 2. Let's say you let a character walk from point A to B, and acceleration is involved. Then ideally you expect the player to end up in the same spot after the same time, independent of your refresh rate. But this won't be the case because your movement speed will vary a bit. In fact, the problem is even amplified by the common practice of normalizing speed in games (the "speed * deltaTime" way of things: an easy but rather coarse way of integrating). Take a Quake-like game and let it run in a variable time step loop (Update()) like that. Consider one player playing it on a 60Hz screen. The other one on a 144Hz screen. They would effectively play two different games, not just because one feels more responsive than the other, but also because the movement mechanics will effectively differ. The actions one player can pull off might not even be possible for the other player. The expectation of an invariant possibilty space is broken.

It shows how important determinism is and why I actually would want it in any game. Simply because I want everyone to play the same game when they actually play the same program. It should ideally not depend on noise in deltaTime and the refresh rate.

So the common practice of doing these things, including arguing against FixedUpdate(), is not because it is the correct way to go about this stuff, but because this way is easier for inexperienced developers and works "well enough". Different views on the "well enough" part is what makes a world of difference and divides developers.


In the words of the docs: "The fixed time step system steps forward at a pre-defined amount each step, and is not linked to the visual frame updates. It is more commonly associated with the physics system, which runs at the rate specified by the fixed time step size, but you can also execute your own code each fixed time step if necessary."

I've never seen info about fixed update breaking and running out of step. Do you have any examples?

[quote=“oscarAbraham”, post:16, topic: 891926]
I’ve never seen info about fixed update breaking and running out of step. Do you have any examples?
[/quote]

This is often a source of confusion: FixedUpdate() will not be called at a fixed time interval measurable by your wrist watch: It is called when a fixed amount of time has passed in-game.

Say that a frame took 0.5 seconds to render. If your fixed timestep is set to 0.02, FixedUpdate() will be called 0.5/0.02 = 25 times in a row at the start of the next frame. You won’t get 25 evenly spaced calls throughout the next few frames.

This is an over simplification, since FixedUpdate uses a time accumulator to determine how much time it needs to simulate (and it can become too much, in which case you face the dreaded spiral of death) but you get the idea. So depending on what our understanding of “running out of step” is, we might be talking about completely different things or about the exact same stuff.

Using FixedUpdate() for things other than physics is justified when a variable time delta is not good enough, for instance when implementing your own kinematic character controller. [RetroMasters82]( https://discussions.unity.com/t/891926 members/retromasters82.8440414/)'s needs are quite common.

1 Like

An easy way to think about FixedUpdate is to know that when it is called, it is used to indicate that you can now assume that at least a fixed interval has passed (Time.fixedDeltaTime). If 6 x Time.fixedDeltaTime have passed then it'll be called 6 times to "catch-up" to game-time. So yes, they can be called back to back but the code inside the fixed-update need not care about that. On average, it'll be called such that it keeps up with game-time (realtime).

Everything in Unity is on the main-thread so it's all sequential. FixedUpdate (x how many needed to catch-up or zero if not required yet) then Update. Repeat.

As above, the spiral-of-death is a consequence of something taking too much time. Let's say it's rendering or user scripts. This causes the fixed-update to be behind so multiple fixed-updates are called. If the code in them takes a long time (let's say some complex physics) then that puts fixed-update itself behind time. This is a bad feedback which has to be limited/capped because the alternative is the spiral-of-death mentioned above. For some, this death is defined by "juddering". They'll see physics taking ages and suggest it's that. The real problem might be scripts/rendering taking too long causing multiple fixed-updates.

4 Likes

As a separate post. This is easy to test out. Create a script that takes a lot of time, maybe you can control this via the inspector then dump a log to the console each time the fixed-update is called and each time the update is called. You'll see multiple fixed-updates.

An alternative is setting the fixed-update to something crazy which honestly, I see devs doing. So set the fixed-update to 1Khz (1ms). You'll see lots of fixed-updates as it's trying to maintain (on average) 1000 calls/secs.

An even easier way is to spin-up the profiler and record. You'll soon find FixedUpdate with more than a single count in the "Calls" column.

I just want to add that this page in the docs has a really nice diagram explaining how FixedUpdate works.

1 Like