HOW TO BUILD A SMOOTH 3D FOLLOW CAMERA IN UNITY

Eliminate camera stutter and player jitter using LateUpdate, Vector3.SmoothDamp spring interpolation, rotational orientation tracking, and obstacle collision clipping.

1. Why Most Third-Person Cameras Jitter

Camera jitter is almost always caused by an execution order race condition:

  • If the camera script updates its position in Update(), it might execute before the player's movement script has completed its move, causing the camera to lag behind by exactly one frame.
  • If using simple linear interpolation (Vector3.Lerp) without delta time correction, the camera speed varies depending on whether the game is running at 30 FPS or 120 FPS.

The Solution: Run the camera tracking exclusively inside LateUpdate() and use Vector3.SmoothDamp with a calibrated smooth time (e.g. 0.15s).

2. Production Smooth Follow Camera Script

Attach this script directly to your Main Camera:

using UnityEngine;

/// <summary>
/// Jitter-free third-person follow camera with smooth damping and wall collision avoidance.
/// </summary>
public class SmoothFollowCamera : MonoBehaviour
{
    [Header("Target & Offsets")]
    [Tooltip("The player, vehicle, or aircraft transform to follow.")]
    [SerializeField] private Transform target;

    [Tooltip("Desired camera offset in target local space (X: side, Y: height, Z: distance behind).")]
    [SerializeField] private Vector3 offset = new Vector3(0f, 2.5f, -6f);

    [Header("Smoothing")]
    [Tooltip("Time in seconds to catch up to target position.")]
    [Range(0.01f, 0.5f)]
    [SerializeField] private float smoothTime = 0.12f;

    [Tooltip("Speed of rotational alignment toward target heading.")]
    [SerializeField] private float rotationSpeed = 8f;

    [Header("Collision Zoom")]
    [Tooltip("LayerMask containing solid terrain and structures.")]
    [SerializeField] private LayerMask collisionLayers;
    [SerializeField] private float minDistance = 1.0f;

    private Vector3 currentVelocity; // Internal buffer for SmoothDamp

    private void LateUpdate()
    {
        if (target == null) return;

        // 1. Calculate ideal position in target's local orientation
        Vector3 desiredPosition = target.position + (target.rotation * offset);

        // 2. Raycast to prevent clipping through solid geometry
        Vector3 targetFocusPoint = target.position + Vector3.up * 1.5f;
        Vector3 cameraDirection = desiredPosition - targetFocusPoint;
        float targetDistance = cameraDirection.magnitude;

        if (Physics.Raycast(targetFocusPoint, cameraDirection.normalized, out RaycastHit hit, targetDistance, collisionLayers))
        {
            // Pull camera forward in front of obstacle
            desiredPosition = targetFocusPoint + (cameraDirection.normalized * Mathf.Max(minDistance, hit.distance - 0.2f));
        }

        // 3. Smoothly interpolate position with critically damped spring
        transform.position = Vector3.SmoothDamp(transform.position, desiredPosition, ref currentVelocity, smoothTime);

        // 4. Smoothly rotate camera toward target focus point
        Quaternion targetRotation = Quaternion.LookRotation(targetFocusPoint - transform.position);
        transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
    }
}

3. Recommended Parameter Configuration

For Humanoid Characters
Offset: (0.5, 1.8, -3.5) (Over-the-shoulder)
SmoothTime: 0.08s (Snappy)
RotationSpeed: 12
For Arcade Cars & Jets
Offset: (0, 3.2, -8.0) (Chasing)
SmoothTime: 0.18s (Cinematic drift feel)
RotationSpeed: 5.5

Related Topics & Developer Tools

Frequently Asked Questions

Vector3.SmoothDamp implements a spring-damper physical simulation model that prevents sudden jerky acceleration or overshoot. It uses an exact time duration (smoothTime) to reach the target, guaranteeing mathematically smooth camera movement regardless of framerate spikes.
Never parent a follow camera under a moving player in a 3D third-person game. Parenting causes the camera to rigidly lock to every tiny physical tilt, collision bounce, and instant turn of the player mesh, causing severe motion sickness. Always keep the camera at the root of the scene and track the player via script in LateUpdate().