Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411507 Posts in 69374 Topics- by 58429 Members - Latest Member: Alternalo

April 26, 2024, 01:32:59 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)Custom Motion Interpolation in Unity (plus stable FixedUpdate())
Pages: [1]
Print
Author Topic: Custom Motion Interpolation in Unity (plus stable FixedUpdate())  (Read 2831 times)
J-Snake
Level 10
*****


A fool with a tool is still a fool.


View Profile WWW
« on: August 21, 2022, 01:45:45 PM »

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://forum.unity.com/threads/sta...motion-interpolation-global-solution.1547513/

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:


Code:
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 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()
    {
        if (isTransformInterpolated)        //reset transform to its supposed current state just once after each Update/Drawing
        {
            transform.localPosition = transformData.position;
            transform.localRotation = transformData.rotation;
            transform.localScale    = transformData.scale;

            isTransformInterpolated = false;
        }

        //cache current transform state (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()
    {
        if (!isTransformInterpolated)  //cache the updated transform in transformData so that it can be restored in FixedUpdate() after drawing
        {
            transformData.position = transform.localPosition;
            transformData.rotation = transform.localRotation;
            transformData.scale    = transform.localScale;

            isTransformInterpolated = true; //it's ok to set it here since the anticipation matches the execution that follows
        }

        float interpolationAlpha = (Time.time - Time.fixedTime) / Time.fixedDeltaTime; //(Time.time - Time.fixedTime) is the "unprocessed" time according to documentation
        
        transform.localPosition = Vector3.Lerp(prevTransformData.position, transformData.position, interpolationAlpha);        //interpolate transform
        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.


« Last Edit: March 12, 2024, 03:55:15 AM by J-Snake » Logged

Independent game developer with an elaborate focus on interesting gameplay, rewarding depth of play and technical quality.<br /><br />Trap Them: http://store.steampowered.com/app/375930
michaelplzno
Level 10
*****



View Profile WWW
« Reply #1 on: August 27, 2022, 05:41:39 AM »

interesting bit, I'll make a video comparison of some of these time loops based on your code.
Logged

J-Snake
Level 10
*****


A fool with a tool is still a fool.


View Profile WWW
« Reply #2 on: March 12, 2024, 04:11:01 AM »

I have complemented this thread by a stable FixedUpdate() scheduler, as some wanted to know how to transfer an abstract solution in the paper into Unity's concrete engine framework. This is also a great example for "Why reinventing the wheel?". Simply because we don't always get good wheels, but good wheels are fundamental. So here it goes:
https://forum.unity.com/threads/sta...motion-interpolation-global-solution.1547513/
Logged

Independent game developer with an elaborate focus on interesting gameplay, rewarding depth of play and technical quality.<br /><br />Trap Them: http://store.steampowered.com/app/375930
Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic