U
tab 03Camera & Input6 lessons — tap to open
01 / 06 · 8 min
Camera & Input

Follow camera

your project so far

Assets/

Scenes/

Arena.unity

Knight

Scripts/

CharacterSwitcher.csSwapCamera.cs→FollowCamera.cs

you already havethis lesson addsthis lesson retires

Open Arena.unity. SwapCamera from tab 01 already follows whoever is active, but it snaps, it stares at the feet, and it happily slides through walls. This tab rebuilds it in three steps, and this is step one.

A camera glued to the player feels cheap. A camera that chases the player with a slight lag feels like a game.

animated diagram

The player moves first, the camera catches up over the next few frames.

The follow script

FollowCamera.cs — remove SwapCamera from Main Camera first
using UnityEngine;

public class FollowCamera : MonoBehaviour
{
    [SerializeField] private Transform target;
    [SerializeField] private Vector3 offset = new Vector3(0f, 6f, -8f);
    [SerializeField] private float smoothTime = 0.15f;
    [SerializeField] private Vector3 lookAtOffset = new Vector3(0f, 1.5f, 0f);

    private Vector3 velocity;      // SmoothDamp keeps its own state here

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

        Vector3 wanted = target.position + offset;
        transform.position = Vector3.SmoothDamp(transform.position, wanted, ref velocity, smoothTime);
        transform.LookAt(target.position + lookAtOffset);
    }
}

Lerp or SmoothDamp?

✓ Vector3.Lerp(a, b, t * dt)

Easy to read, but the actual smoothing changes slightly with frame rate.

✓ Vector3.SmoothDamp(...)

Spring-like, frame-rate independent, and smoothTime is in real seconds.

Keep following the active character

Hard-coding target to the Knight would break pressing 2 and 3. Ask the switcher instead — exactly as SwapCamera did.

C# script
[SerializeField] private CharacterSwitcher switcher;

void LateUpdate()
{
    Transform target = switcher.ActiveCharacter;    // not a fixed reference
    Vector3 wanted = target.position + offset;

    transform.position = Vector3.SmoothDamp(transform.position, wanted, ref velocity, smoothTime);
    transform.LookAt(target.position + lookAtOffset);
}

Why LateUpdate, always

  • Unity runs every Update in an undefined order. Your camera might run before the player moves.

  • LateUpdate runs after all Updates are done, so the camera always sees the final position of that frame.

  • Symptom of getting this wrong: the character visibly vibrates by one or two pixels while moving.

Look-ahead — see where you are going

Shifting the camera slightly in the direction of travel gives the player more warning about what is coming. It costs three lines.

C# script
[SerializeField] private float lookAhead = 2.5f;
private CharacterController targetCC;

void LateUpdate()
{
    Vector3 ahead = targetCC.velocity.normalized * lookAhead;
    ahead.y = 0f;

    Vector3 wanted = target.position + offset + ahead;
    transform.position = Vector3.SmoothDamp(transform.position, wanted, ref velocity, smoothTime);
    transform.LookAt(target.position + lookAtOffset);
}

Stop the camera clipping through walls

add before assigning the position
[SerializeField] private LayerMask obstacles;

Vector3 pivot = target.position + lookAtOffset;
Vector3 dir = (wanted - pivot).normalized;
float dist = Vector3.Distance(pivot, wanted);

// is there a wall between the character and where the camera wants to be?
if (Physics.SphereCast(pivot, 0.3f, dir, out RaycastHit hit, dist, obstacles))
    wanted = hit.point - dir * 0.25f;      // pull the camera in front of it

Common mistakes

  • smoothTime of 0 snaps like the old SwapCamera did; above 0.5 s the camera lags so far behind that the player runs off screen. Keep it between 0.1 and 0.25 s and tune from there.

  • Declaring velocity inside LateUpdate resets SmoothDamp every frame, and the motion turns springy and wrong. It must stay a field, because SmoothDamp keeps its state there between frames.

  • Main Camera dragged under the character in the Hierarchy inherits every rotation of its parent and fights this script. Keep the camera at the root of the scene and let the script do the following.