Part of these game systems:
beginner Movement

Smooth Camera Follow 2D

Smooth camera follow with offset, dead zone, and look-ahead for 2D games.

Unity 2022.3+ · 1.8 KB · CameraFollow2D.cs

How to Use

1

Attach to your Main Camera

2

Drag your player into the Target field

3

Adjust offset and smooth speed

4

Optionally enable bounds to limit camera area

Features

  • Smooth Lerp-based camera following with adjustable speed
  • Configurable offset for custom framing
  • Look-ahead in movement direction for better visibility
  • Optional camera bounds to clamp within level edges
  • Null-safe target check prevents errors if player is destroyed
  • Works in LateUpdate for jitter-free rendering

When to Use This

Ideal for 2D platformers, side-scrollers, and top-down 2D games that need a camera smoothly tracking the player. Use this when you want a polished camera that looks ahead of the player's movement and stays within defined level boundaries. Pairs naturally with any 2D player controller.

Common Mistakes

Forgetting to set the Z offset to -10 (or your camera's Z position) will place the camera at the same depth as sprites, making nothing visible. If useBounds is enabled but minBounds/maxBounds are left at zero, the camera will be locked to the origin. Make sure the Target field is assigned — the script silently returns if target is null.

Source Code

CameraFollow2D.cs
C#
using UnityEngine;

public class CameraFollow2D : MonoBehaviour
{
    [Header("Target")]
    [SerializeField] private Transform target;

    [Header("Follow Settings")]
    [SerializeField] private Vector3 offset = new Vector3(0f, 1f, -10f);
    [SerializeField] private float smoothSpeed = 8f;

    [Header("Look Ahead")]
    [SerializeField] private float lookAheadDistance = 2f;
    [SerializeField] private float lookAheadSpeed = 4f;

    [Header("Bounds (Optional)")]
    [SerializeField] private bool useBounds;
    [SerializeField] private Vector2 minBounds;
    [SerializeField] private Vector2 maxBounds;

    private float currentLookAhead;

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

        // Look ahead in movement direction
        float targetLookAhead = Input.GetAxisRaw("Horizontal") * lookAheadDistance;
        currentLookAhead = Mathf.Lerp(currentLookAhead, targetLookAhead, Time.deltaTime * lookAheadSpeed);

        Vector3 desiredPosition = target.position + offset;
        desiredPosition.x += currentLookAhead;

        Vector3 smoothedPosition = Vector3.Lerp(transform.position, desiredPosition, smoothSpeed * Time.deltaTime);

        // Clamp to bounds
        if (useBounds)
        {
            smoothedPosition.x = Mathf.Clamp(smoothedPosition.x, minBounds.x, maxBounds.x);
            smoothedPosition.y = Mathf.Clamp(smoothedPosition.y, minBounds.y, maxBounds.y);
        }

        transform.position = smoothedPosition;
    }
}