Understanding rotation jitter in this simplistic example

Based on the Unity docs, the execution order per frame is FixedUpdate => Update => LateUpdate

In the small test script below I change the rotation of the player’s transform to whatever the current value of cameraYRot is specified to. This maybe gets called several times, but there is no collider, smoothing or physics at all and so the rotation should be exactly that value after FixedUpdate.

Then Update gets called by the engine and here I set the cameraRotationPivot to the value cameraYRot. So both GameObjects should have now the same rotation => the player and the camera should look into the same direction.

In LateUpdate I’m reading the new cameraYRot from the user’s input. This doesn’t get used this frame anymore. And everything gets rendered.

I would expect that the player and the Camera have the same rotation and no jitter happens, but I get a jittery rotation and appreciate any help to understand this problem.

The script below has several modes for reading the input (Update, LateUpdate) and also several modes for updating the camera rotation to play around with.

Info: You can drop this script in an empty scene to any existing GameObject and it will create all the needed stuff + you can play around with the 2 setting modes to check the results.

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

public class Test_RotJitter_2 : MonoBehaviour
{
    // some update modes to play around and test different settings
    public CamUpdateModes CameraUpdateMode = CamUpdateModes.Update_BeforeRead;
    public InputUpdateModes InputUpdateMode = InputUpdateModes.Update;

    // the desired y-rotation
    public float cameraYRot;
    public float rotationSpeed = 1000;

    // a character or cube, where you can see jitter effects (capsule is rather bad)
    public Transform player;

    // the Pivot is an empty GameObject placed at exactly the same position as the player
    // it contains the MainCamera, put at any angle you want to look at the player
    public Transform cameraRotationPivot;

    // the input from the user
    public float xAxisInput;

    public enum InputUpdateModes
    {
        Update, LateUpdate
    }

    public enum CamUpdateModes
    {
        Update_BeforeRead, Update_AfterRead,
        FixedUpdate_BeforeMove, FixedUpdate_AfterMove,
        LateUpdate_BeforeRead, LateUpdate_AfterRead
    }

    private void Reset()
    {
        GameObject pivot = new GameObject();
        cameraRotationPivot = pivot.transform;
        cameraRotationPivot.name = "Camera Rotation Pivot";

        GameObject goPlayer = GameObject.CreatePrimitive(PrimitiveType.Cube);
        player = goPlayer.transform;
        player.name = "PLayer";
        player.position = new Vector3(0, 3, 0);

        cameraRotationPivot.position = player.position;
        Transform camTransform = Camera.main.transform;
     
        camTransform.position = player.position - player.forward * 5 + Vector3.up;
        camTransform.rotation = Quaternion.Euler(15, 0, 0);
        camTransform.parent = cameraRotationPivot;
    }

    void ReadInput()
    {
        xAxisInput = Input.GetAxis("Mouse X");
        cameraYRot = cameraYRot + xAxisInput * rotationSpeed * Time.deltaTime;
    }

    void RotateCamera()
    {
        cameraRotationPivot.rotation = Quaternion.Euler(0, cameraYRot, 0);
    }

    // we always rotate the player in FixedUpdate,
    // because later the physics needs to be done here
    // we never read input in FixedUpdate, because it can be called several times
    private void FixedUpdate()
    {
        if (CameraUpdateMode == CamUpdateModes.FixedUpdate_BeforeMove) RotateCamera();
        player.rotation = Quaternion.Euler(0, cameraYRot, 0);
        if (CameraUpdateMode == CamUpdateModes.FixedUpdate_AfterMove) RotateCamera();
    }

    // Update is called once per frame
    void Update()
    {
        if (CameraUpdateMode == CamUpdateModes.Update_BeforeRead) RotateCamera();
        if (InputUpdateMode == InputUpdateModes.Update) ReadInput();
        if (CameraUpdateMode == CamUpdateModes.Update_AfterRead) RotateCamera();
    }

    private void LateUpdate()
    {
        if (CameraUpdateMode == CamUpdateModes.LateUpdate_BeforeRead) RotateCamera();
        if (InputUpdateMode == InputUpdateModes.LateUpdate) ReadInput();
        if (CameraUpdateMode == CamUpdateModes.LateUpdate_AfterRead) RotateCamera();
    }

}

Is the cameraRotationPivot child of player?
If yes, this probably explains your problem since player is always rotated in FixedUpdate.

Thank you for your fast response.

No the cameraRotationPivot is not child of the player. The MainCamera is parented as a child to cameraRotationPivot, but the cameraRotationPivot and player are not parented in any way.

Ok I looked into it and found the problem. The reason is because you are updating the player in a different update loop than your camera. For example if your player is being rotated in FixedUpdate he will update every FixedUpdate, by default this is 50 FPS. Your Update and LateUpdate however will run at your max framerate which can be like 500+ in an almost empty scene. Therefore the player will always look like he is lagging behind, because he actually is :). Since the player rotation is independent from the camera in your test scenario, it won’t matter if you rotate the player first or the camera. The only thing that matters for this test scenario is FixedUpdate vs Update/LateUpdate ;). Hope this helps.

First I understood this only partially, but now after reading the docs over and over again it gets clearer.

Writing the important things down:

  • if you have physics, you should update your character in FixedUpdate only (hope this is right)
  • all the resources I’ve found earlier say, you do not want to read your inputs in FixedUpdate
    but I do not understand why, since moving and camera rotating are important, happen in FixedUpdate and depend on the input, so why read an input in for example Update, when it maybe get’s lost, because it will have only an effect when FixedUpdate happen AND FixedUpdate happens sometimes 2x per frame, sometimes only 1x per 2 rendered frames.

=> especially the case that a frame can get rendered without having FixedUpdate involved can lead to an InputRead of the y-rotation which will not affect the rotation of the character. If you update the camera in FixedUpdate (looks like the only way of having no stuttering) then the rotations do not jitter but are not in sync with your input.

Actually, ONE correct way of doing a physics-based character controller can be:

  1. Do input in Update
  2. Cache the input to later use it in FixedUpdate
  3. Do physics-forces stuff in FixedUpdate
  4. Sync your corresponding Transform of your Rigidbody (FixedUpdate → Update synchronization)
    …a) setting the Rigidbodys interpolate mode from None to Interpolate
    …b) or instead writing your own custom Transform synchronization logic
  5. Do Camera in Update
  6. Do character rotation in Update (in case you chose to go with 4a)

However the last time I did a physics-based character controller is years ago so this is from the top of my head and it may have incorrect statements that I remember wrongly (I hope it does not :smile:).

Doing character controllers fully in FixedUpdate without any snychronization to Update is a bad idea because it will limit your characters smoothness to your FixedUpdate-rate (by default 50FPS).

The CharacterController component works differently - it works already with the Update-rate instead of FixedUpdate.
If your character doesn’t have to interact with physics much I actually recommend using the CharacterController component or getting a character controller from the Asset Store that solves this problem correctly.
Or you write your own custom solution, however this will be challenging.

EDIT:
Newer Unity versions like Unity 2023 have support for changing the physics simulation mode to Update instead of FixedUpdate - however I haven’t tested this mode yet.

1 Like

Thank you a lot for all your help! Will need to study your words in depth.

1 Like