Smooth Camera Follow in Godot 4
The default Camera2D position smoothing feels floaty and unresponsive. Here's a proper camera system with lookahead, deadzone, and weighted smoothing.
The problem with basic lerp
Most Godot tutorials tell you to lerp the camera position toward the player. It works, but it creates a “floaty” feeling — the camera constantly lags behind, especially during fast direction changes. Players feel disconnected from their character.
# The "basic lerp" approach everyone uses
# Problem: always feels sluggish and unresponsive
func _process(delta):
position = position.lerp(target.position, 5.0 * delta)The fix: weighted smoothing + deadzone
The key insight is using two different smoothing weights: a fast weight when the player moves outside the deadzone, and a slower weight when they're inside it. This gives you snappy response during action and stability during idle.
extends Camera2D
@export var target: Node2D
# Deadzone — camera won't move for small player movements
@export var deadzone := Vector2(40.0, 30.0)
# Smoothing weights (higher = snappier)
@export var fast_weight := 12.0
@export var slow_weight := 3.0
# Lookahead — camera leads the player's movement
@export var lookahead_strength := 60.0
@export var lookahead_smoothing := 5.0
var _lookahead_target := Vector2.ZERO
var _current_lookahead := Vector2.ZERO
func _physics_process(delta: float) -> void:
if not target:
return
var target_pos := target.global_position
var diff := target_pos - global_position
# Calculate lookahead based on player velocity
if target is CharacterBody2D:
var vel := (target as CharacterBody2D).velocity
_lookahead_target = vel.normalized() * lookahead_strength
_current_lookahead = _current_lookahead.lerp(
_lookahead_target, lookahead_smoothing * delta
)
# Pick weight: fast outside deadzone, slow inside
var weight := slow_weight
if abs(diff.x) > deadzone.x or abs(diff.y) > deadzone.y:
weight = fast_weight
# Apply smoothed movement + lookahead offset
var goal := target_pos + _current_lookahead
global_position = global_position.lerp(goal, weight * delta)Why this works
Prevents jitter when the player makes tiny movements. Camera stays locked until the player moves far enough.
Fast weight (12.0) snaps to the player during action. Slow weight (3.0) drifts gently during idle.
Camera leads the player's movement direction so they can see what's ahead. Essential for platformers.
Using _physics_process instead of _process syncs with CharacterBody2D movement. No jitter.
Bonus: screen shake in 5 lines
Add this to the same script for juicy screen shake on hit:
var _shake_intensity := 0.0
var _shake_decay := 8.0
func shake(intensity: float = 8.0) -> void:
_shake_intensity = intensity
# Add to end of _physics_process:
_shake_intensity = lerp(_shake_intensity, 0.0, _shake_decay * delta)
offset = Vector2(
randf_range(-1, 1) * _shake_intensity,
randf_range(-1, 1) * _shake_intensity
)Call camera.shake(12.0) from your damage handler and you're done. Buttery smooth, responsive, and juicy.
Want the full video walkthrough?
The complete “Smooth Camera Follow” course covers camera limits, multi-target tracking, cinematic pans, and advanced shake patterns — with a ready-to-use project file. 15 minutes, $3.99.
Get the Full Course →