This post is useful for those who are interested in a custom solution for motion interpolation in Unity. The goal is to achieve stutter-free visuals for deterministic game logic that runs in FixedUpdate(). This is easily achieved by a single TransformInterpolator script. It is also a general one-size-fits-all solution that aims to automate all of the caretaking to prevent interpolation glitches from happening, in any edge case. Just use it as documented in few lines and you pretty much cannot go wrong. It will âjust workâ.
If you are new to the topic of fixed timestep loops and motion-interpolation, here is an introduction to that:
And here is the complementary thread that shows how to make fixed timestep logic more responsive and potentially even more performant:
And this here documents how FixedUpdate() is timed in Unity:
And here is the script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public static class TransformUtils // Convenience functions:
{
/// <summary>
/// A SetParent() wrapper that works in a safe way and doesn't expose interpolation glitches.
/// </summary>
/// <param name="t"></param>
/// <param name="parent"></param>
/// <param name="transformLocalSpace"></param>
public static void SetParentInterpolated(this Transform t, Transform parent, bool transformLocalSpace = true)
{
if (parent == t.parent) return;
TransformInterpolator transformInterpolator = t.GetComponent<TransformInterpolator>();
if (transformInterpolator != null && transformInterpolator.enabled)
{
// Skip interpolation for this frame:
transformInterpolator.enabled = false;
t.SetParent(parent, transformLocalSpace);
transformInterpolator.enabled = true;
}
else
t.SetParent(parent, transformLocalSpace);
}
/// <summary>
/// A SetLocalPositionAndRotation()/SetPositionAndRotation() wrapper that suppresses interpolation.
/// </summary>
/// <param name="t"></param>
/// <param name="position"></param>
/// <param name="rotation"></param>
/// <param name="transformLocalSpace"></param>
public static void Teleport(this Transform t, Vector3 position, Quaternion rotation, bool transformLocalSpace = true)
{
TransformInterpolator transformInterpolator = t.GetComponent<TransformInterpolator>();
if (transformInterpolator != null && transformInterpolator.enabled)
{
// Skip interpolation for this frame:
transformInterpolator.enabled = false;
if (transformLocalSpace)
t.SetLocalPositionAndRotation(position, rotation);
else
t.SetPositionAndRotation(position, rotation);
transformInterpolator.enabled = true;
}
else
{
if (transformLocalSpace)
t.SetLocalPositionAndRotation(position, rotation);
else
t.SetPositionAndRotation(position, rotation);
}
}
}
/// <summary>
/// How to use TransformInterpolator properly:
/// 0. Make sure the gameobject executes its mechanics (transform-manipulations)
/// in FixedUpdate().
/// 1. Set the execution order for this script BEFORE all the other scripts
/// that execute mechanics.
/// 2. Attach (and enable) this component to every gameobject that you want to interpolate
/// (including the camera).
/// </summary>
public class TransformInterpolator : MonoBehaviour
{
void OnDisable() // Restore current transform state:
{
if (isTransformInterpolated_)
{
transform.localPosition = transformData_.Position;
transform.localRotation = transformData_.Rotation;
transform.localScale = transformData_.Scale;
isTransformInterpolated_ = false;
}
isEnabled_ = false;
}
void FixedUpdate()
{
// Restore current transform state in the first FixedUpdate() call of the frame.
if (isTransformInterpolated_)
{
transform.localPosition = transformData_.Position;
transform.localRotation = transformData_.Rotation;
transform.localScale = transformData_.Scale;
isTransformInterpolated_ = false;
}
// Cache current transform as the starting point for interpolation.
prevTransformData_.Position = transform.localPosition;
prevTransformData_.Rotation = transform.localRotation;
prevTransformData_.Scale = transform.localScale;
}
void LateUpdate() // Interpolate in Update() or LateUpdate().
{
// The TransformInterpolator could get enabled and then modified in this frame.
// So we postpone the enabling procedure to refresh the starting point of interpolation.
// And we just skip interpolation for the first frame.
if (!isEnabled_)
{
OnEnabledProcedure();
return;
}
// Cache the final transform state as the end point of interpolation.
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 void OnEnabledProcedure() // Captures initial transform state.
{
prevTransformData_.Position = transform.localPosition;
prevTransformData_.Rotation = transform.localRotation;
prevTransformData_.Scale = transform.localScale;
isEnabled_ = true;
}
private struct TransformData
{
public Vector3 Position;
public Vector3 Scale;
public Quaternion Rotation;
}
private TransformData transformData_;
private TransformData prevTransformData_;
private bool isTransformInterpolated_ = false;
private bool isEnabled_ = false;
}
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 interpolate animations that are running in FixedUpdate() (AnimatePhysics-mode).
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.
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.
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?
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: Fix Your Timestep! | Gaffer On Games
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.
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.
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.
Reproduceable edge cases the player can rely on
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?
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.
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.
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.