Weird Camera Shake on Collider Check

I have my camera readjusting on collision, via a linecast, and i’m not sure why but it does kinda a jerky movement… Well, i assume what’s happening is it’s readjusting from a collision, then checking again and readjusting back, but i’m not quite sure how to fix this…
Here’s a video,

I’ve spent hours trying to fix this, watched a million tutorials, even tried Cinemachine(which was subpar from my experience lol).

This is my camera script,

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

public class CameraMovement : MonoBehaviour
{
    Vector3 rotGoal;

    [SerializeField] float rotSpeed = 1, rotSens = 2, zoomSpeed = 3, speedZoomMulti = 5, maxSpeedZoom = 15, reAdjustSpeed = 5, defaultXAngle = 10;
    [SerializeField] Vector2 maxXRotation;

    [SerializeField] Vector3 offset;

    Vector2 input;

    public Transform target;
    Player player;

    [SerializeField] float mobileCamAngle = 20;
    float currentOffset;

    [SerializeField] LayerMask layersToIgnore;

    [SerializeField] bool followPlayer = true, lockRotation, lockCamera;

    Transform camT;
    bool shaking;

    float currentSpeedZoom;
    [SerializeField] Vector2 speedZoomLevels;
    [SerializeField] float shakeDistance = 1, shakeSpeed = 5, distanceToShake = 30, minRange = 0.5f;
    [SerializeField] int shakeCount;
    Vector3 basePos;

    Vector3 previousPos;
    float velocity;

    public static CameraMovement instance;

    private void Awake()
    {
        transform.parent = null;
        instance = this;
        camT = Camera.main.transform;
        basePos = offset;

        player = FindObjectOfType<Player>();
    }

    void Start()
    {

        if (!GlobalValues.isMobile)
        {
            Cursor.lockState = CursorLockMode.Locked;
            Cursor.visible = false;
        }
        else
        {
            maxXRotation = Vector2.one * mobileCamAngle;
        }

        currentOffset = offset.z;

        ResetCamera(target.position);
    }

    public void ToggleCamera(bool lockRotation, bool lockCamera)
    {

        this.lockRotation = lockRotation;
        this.lockCamera = lockCamera;

        if (lockRotation)
        {
            rotGoal = transform.eulerAngles;
        }

        if (!followPlayer)
        {
            target = Camera.main.transform;
        }
    }

    public void ResetCamera(Vector3 pos)
    {
        currentSpeedZoom = 0;
        transform.eulerAngles = rotGoal;
        transform.position = pos + basePos;
        previousPos = pos;
    }

    private void Update()
    {
        input = InputManager.input.Player.Look.ReadValue<Vector2>().normalized * rotSens;

    }

    void FixedUpdate()
    {
        if (lockCamera) return;
        velocity = Mathf.Abs((target.position - previousPos).magnitude);
        previousPos = target.position;
        currentSpeedZoom = Mathf.Clamp(Mathf.Lerp(currentSpeedZoom, velocity * speedZoomMulti, zoomSpeed * Time.deltaTime), 0, maxSpeedZoom);

        if (followPlayer) target = target.transform;

        Vector3 newCamPos = target.position - transform.forward * currentOffset + Vector3.up * offset.y;

        RaycastHit hit;

        if (Physics.Linecast(target.position, newCamPos, out hit, ~layersToIgnore, QueryTriggerInteraction.Ignore))
        {
            if (currentOffset > 1) currentOffset = hit.distance;
            
        }
        else
        {
            if (currentOffset != offset.z) currentOffset = Mathf.MoveTowards(currentOffset, offset.z + currentSpeedZoom, Time.deltaTime * zoomSpeed);
        }

        if (!lockRotation)
        {
            rotGoal += new Vector3(-input.y, input.x);
            rotGoal.x = Mathf.Clamp(Mathf.LerpAngle(rotGoal.x, defaultXAngle, reAdjustSpeed * player.animSpeed * Time.deltaTime), maxXRotation.y, maxXRotation.x);

            rotGoal.y = Mathf.LerpAngle(rotGoal.y, target.transform.eulerAngles.y, reAdjustSpeed * player.animSpeed * Time.deltaTime);
        }

        transform.position = newCamPos;


        if (GlobalValues.isMobile)
        {
            rotGoal.y = target.eulerAngles.y;
        }

        rotGoal.z = 0;
        transform.localEulerAngles = new Vector3(transform.eulerAngles.x, transform.eulerAngles.y, 0);
        transform.localRotation = Quaternion.Lerp(transform.rotation, Quaternion.Euler(rotGoal), rotSpeed * Time.deltaTime);

    }

    public void OnDeath()
    {
        target = GameObject.Find("DeadCameraAnchor").transform;

        ToggleCamera(true, true);

        transform.parent = target;

        transform.localPosition = Vector3.zero;

        transform.localEulerAngles = new Vector3(35, 90f, 0);
    }

    public void ShakeCamera(float distance)
    {
        if (shaking) return;
        shaking = true;
        basePos = camT.localPosition;

        float distanceMulti = (distanceToShake / distance) / 15;

        StopAllCoroutines();
        if (distanceMulti < 0.05f) return;
        StartCoroutine(ShakeRoutine(distanceMulti));
    }

    public IEnumerator ShakeRoutine(float distanceMulti)
    {

        camT.transform.localPosition = basePos;
        for (int i = shakeCount; i > -1; i--)
        {
            Vector3 newPos = basePos + new Vector3(Random.Range(-1f, 1f), Random.Range(-1f, 1f), 0) * shakeDistance * distanceMulti;
            while (camT.transform.localPosition != newPos)
            {
                camT.transform.localPosition = Vector3.MoveTowards(camT.transform.localPosition, newPos, shakeSpeed + distanceMulti * Time.deltaTime);
                yield return null;
            }
        }

        while (camT.transform.localPosition != basePos)
        {
            camT.transform.localPosition = Vector3.MoveTowards(camT.transform.localPosition, basePos, shakeSpeed + distanceMulti * Time.deltaTime);
            yield return null;
        }
        shaking = false;
    }

}

(I’ve tried in both fixed and late update, for some reason it’s even more jerky in late update)

Sorry for the lack of detail, not sure what other information i can provide. Does anyone know a good solution for this?
Thanks in advance

This looks like some kind of hysteresis, with the position being shoved between different positions each frame.

Frame1 - it is in collider, shove it forward
Frame2 - it is not in collider so push it back to where it wants to be
Frame1 - back to in collider, shove it forward

If this is the case (TEST for it first to prove it!!!), here are some various solutions to consider:

  • make sure there is an available “deadband” area between the two directions.

  • introduce a heavy damping term that kicks in when direction of motion (in/out) changes

  • track a different “desired” position and don’t begin to move towards it until a certain distance away is achieved. This lets the desired position oscillate between positions while the camera itself simply says 'Not far enough for me to move" and doesn’t move.

Here’s some more discussions:

Thanks for the reply, sorry for the late response, it sorta worked.

I tried checking the distance of the transform.position to the newCamPos and it made all camera movements jagged.

So, then I tried this:

if (hit.distance > minOffset && Mathf.Abs(currentOffset - hit.distance) > distanceBeforeMoving) currentOffset = hit.distance;

But it makes it kinda choppy when moving along colliders. It’s certainly an improvement, but it’s because it’s not moving smoothly anymore because it only moves if the distance is far enough. I tried using Mathf.MoveTowards and for some reason it looks even worse