how to prevent jitter when one of the vcam is driven by a rigidbody controller?

Before I switched to cinemachine I had this script move the camera, camera has a rigidbody on it to prevent collision, when game is paused the camera still functions. it’s clunky and doesn’t always work but it doesn’t jitter
when i switched to cinemachine things got easier but also this fly cam now jitters a lot. what’s the solution?


nasty code

using UnityEngine;

// fly with
public class CinemachineFlyingController : MonoBehaviour
{
    public float mouseSensitivityX = 1;
    public float mouseSensitivityY = 1;
    public float rollSpeed = 2;
    public float walkSpeed = 6f, sprintSpeedModifier = 2f, globalSpeedMultiplier = 60f;
    Vector3 _moveAmount;
    Vector3 _smoothMoveVelocity;
    float _verticalLookRotation;
    Rigidbody _rigidbody;
    public LayerMask layerToAttractorGround;
    public KeyCode buttonToDisengageLook = KeyCode.LeftControl;
    float _currentDistanceToTarget;
    float _sprintModifier => Input.GetButton("Sprint") ? sprintSpeedModifier : 1;
    float currentRollVelocity, smoothRoll;
    GravityAttractor previousclosestAttractor;
    Vector3 closestAttractorPreviousPosition;
    GravityAttractor _closestAttractor;
    Vector3 _closestAttractorDeltaPositioon;

    void Awake()
    {
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
        _rigidbody = GetComponent<Rigidbody>();
    }

    void Update()
    {
        _closestAttractorDeltaPositioon = GetClosestAttractor();
        var disengaged = Input.GetKey(buttonToDisengageLook) || buttonToDisengageLook == KeyCode.None;
        Cursor.lockState = disengaged ? CursorLockMode.None : CursorLockMode.Locked;
        Cursor.visible = disengaged;
        if (!disengaged)
            DoSpaceRotate();
        DoSpaceMove();
    }

    void DoSpaceRotate()
    {
        transform.Rotate(Vector3.left * Input.GetAxis("Mouse Y") * mouseSensitivityY * globalSpeedMultiplier * Time.unscaledDeltaTime, Space.Self);
        transform.Rotate(Vector3.up * Input.GetAxis("Mouse X") * mouseSensitivityX * globalSpeedMultiplier * Time.unscaledDeltaTime, Space.Self);
        smoothRoll = Mathf.SmoothDampAngle(smoothRoll, Input.GetAxis("Roll") * rollSpeed * globalSpeedMultiplier, ref currentRollVelocity, .1f, Mathf.Infinity, Time.unscaledDeltaTime);
        transform.Rotate(Vector3.forward * smoothRoll, Space.Self);
    }

    void DoSpaceMove()
    {
        var inputX = Input.GetAxisRaw("Horizontal");
        var inputY = Input.GetAxisRaw("Vertical");
        var inputUp = Input.GetAxisRaw("Altitude");
        var moveDir = new Vector3(inputX, 0, inputY).normalized;
        moveDir.y = inputUp;
        var targetMoveAmount = moveDir * _sprintModifier * walkSpeed;
        _moveAmount = Vector3.SmoothDamp(_moveAmount, targetMoveAmount, ref _smoothMoveVelocity, .15f, Mathf.Infinity, Time.unscaledDeltaTime);
        var movement = transform.TransformDirection(_moveAmount * globalSpeedMultiplier * Time.unscaledDeltaTime) + _closestAttractorDeltaPositioon / Time.unscaledDeltaTime;
        if (Time.timeScale < .01f && movement.sqrMagnitude > 0)
        {
            if (_rigidbody.isKinematic == false)
            {
                _rigidbody.Sleep();
                _rigidbody.velocity = Vector3.zero;
                _rigidbody.interpolation = RigidbodyInterpolation.None;
                _rigidbody.isKinematic = true;
            }
            var hittingGround = Physics.SphereCast(transform.position, 1f, movement, out var hit, .7f * _sprintModifier, layerToAttractorGround);
            _rigidbody.position += hittingGround ? Vector3.zero : movement * Time.unscaledDeltaTime;
        }
        else
        {
            if (_rigidbody.isKinematic)
            {
                _rigidbody.velocity = Vector3.zero;
                _rigidbody.interpolation = RigidbodyInterpolation.Interpolate;
                _rigidbody.angularVelocity = Vector3.zero;
                _rigidbody.WakeUp();
                _rigidbody.MoveRotation(transform.rotation);
                _rigidbody.isKinematic = false;
                Physics.SyncTransforms();
            }
            _rigidbody.velocity = movement;
        }
    }

    Vector3 GetClosestAttractor()
    {
        var closestAttractorDeltaPositioon = Vector3.zero;
        _closestAttractor = GravityAttractor.GetClosestSurfaceAGravityAttractor(transform.position, layerToAttractorGround);
        if (_closestAttractor != null)
            if (previousclosestAttractor != _closestAttractor)
            {
                previousclosestAttractor = _closestAttractor;
                closestAttractorPreviousPosition = _closestAttractor.transform.position;
            }
            else
            {
                closestAttractorDeltaPositioon = _closestAttractor.transform.position - closestAttractorPreviousPosition;
                closestAttractorPreviousPosition = _closestAttractor.transform.position;
            }
        return closestAttractorDeltaPositioon;
    }
}

What is the update method in your Cinemachine brain? Is changing that to fixed update helps?

The jitter comes from aliasing between the variable-time render frames and the fixed-time physics frames.

@laurentlavigne Try enabling interpolation on your RigidBody. This will interpolate between FixedUpdate frames and set the transform at render time to the interpolated value. Note that if you use interpolation, you must take care to move the RigidBody only in approved manners (using RigidBody.AddForce, or RigidBody.MovePosittion, etc) otherwise interpolation won’t work.

If interpolation is not an option then there are other - less optimal - ways of dealing with this. You will get jitter if the camera moves on a different clock from the things it’s tracking. If the target moves on FixedUpdate, then the camera should also move on FixedUpdate, to minimize jitter. That’s what the CinemachineBrain.UpdateMethod is for - it forces the cameras to update on the specified clock. SmartUpdate is special: tracks the update method of each camera target and updates any cameras tracking those targets on the same clock as the target, if possible. This is useful in situations where not all camera targets update on the same clock. However, this is a compromise solution at best. It’s much better to get everything to update on the same clock - the render clock for smoothest results. If you have RigidBodies that the cameras are tracking or that are driving the cameras, interoplation is best.

EDIT after looking at the “nasty code”: you need to refactor that or you will have endless headaches with jitter. Do everything in FixedUpdate, use the correct RigidBody.Move or ApplyForce methods, and keep interpolation on.

Bingo, I switched tthe brain to Lateupdate and all popping is gone. Thanks Eric!

Got it. I sifted through AStar and objects are moved in FixedUpdate, Update or Lateupdate depending on what’s attached so I set the brain to LateUpdate and either removed the rigidbody or switched to Interpolate, this seems to work, no more jitter.

The problem with FixedUpdate is that it stops being called when timeScale=0 , yeah it’s one of those sim games with pause build

1 Like