The camera is the player's window; bad camera work makes a good game feel awful. This skill covers the engine-neutral camera techniques — smooth follow, deadzones, look-ahead, bounds clamping, third-person orbit with collision, first-person look, and multi-target framing — and maps them onto each engine's camera node or rig.
When not to use: for the magnitude and trigger of screen shake and impact juice, use
game-feel (this skill exposes the shake offset hook it drives). For the engine's concrete
camera node/component setup, use godot-3d-essentials (Camera3D, environment) or the engine
skill. For player movement itself use the engine movement skill (godot-2d-movement). For
performance of many cameras/render targets, see performance-optimization.
SmoothDamp), not a fixed lerp(a, b, 0.1) — that 0.1 is
per-frame and changes with frame rate.# Godot 4.7 Camera2D. Engine-provided smoothing + hard limits + drag margins.
@onready var cam := $Camera2D
func _ready() -> void:
cam.make_current()
cam.position_smoothing_enabled = true
cam.position_smoothing_speed = 6.0 # higher = snappier; lower = floatier
cam.limit_left = 0; cam.limit_top = 0 # clamp to the level rect (pixels)
cam.limit_right = level_width; cam.limit_bottom = level_height
cam.drag_horizontal_enabled = true # built-in deadzone via drag margins
# RIGHT: exponential smoothing — same feel at any FPS. `rate` ~ 5..12.
func _follow(dt: float) -> void:
var t := 1.0 - exp(-rate * dt) # converges correctly regardless of dt
global_position = global_position.lerp(target.global_position, t)
# WRONG: global_position = global_position.lerp(target.global_position, 0.1)
# → faster smoothing at higher FPS; different feel on every machine.
# Unity 6.3 LTS: Vector3.SmoothDamp(transform.position, target.position, ref vel, smoothTime) in
# LateUpdate gives the same spring behavior with built-in frame-rate correction.
# Camera only chases once the target leaves the deadzone box, then aims AHEAD of motion.
func _camera_target(dt: float) -> Vector2:
var to := target.global_position - _focus
var dz := deadzone_half_extents # e.g. Vector2(48, 32)
# Only move the focus by the overflow beyond the deadzone (per axis).
_focus.x += clampf(absf(to.x) - dz.x, 0, INF) * signf(to.x)
_focus.y += clampf(absf(to.y) - dz.y, 0, INF) * signf(to.y)
var lead := target.velocity.normalized() * look_ahead_dist # aim ahead of travel
return _focus + lead
# Godot 4.7. Yaw/pitch a pivot; a SpringArm3D auto-pulls the camera in when blocked.
func _unhandled_input(e):
if e is InputEventMouseMotion:
_yaw -= e.relative.x * sensitivity
_pitch = clampf(_pitch - e.relative.y * sensitivity, -1.2, 0.4) # clamp pitch!
func _process(_dt):
pivot.rotation = Vector3(_pitch, _yaw, 0)
# $SpringArm3D handles wall collision: set spring_length + collision_mask; the child
# Camera3D slides in automatically. RIGHT: spring arm. WRONG: camera clips through walls.
# Unity 6.3 LTS: a Cinemachine 3 CinemachineCamera (namespace Unity.Cinemachine) with an Orbital
# Follow + Cinemachine Deoccluder; the CinemachineBrain on the Camera blends automatically.
game-feel)# Expose an additive offset the game-feel trauma model writes to; follow + shake compose.
var shake_offset := Vector2.ZERO # set each frame by game-feel (trauma^2 * noise)
func _apply(final_focus: Vector2) -> void:
global_position = final_focus + shake_offset # shake rides ON TOP of smooth follow
# Unity Cinemachine: add a CinemachineBasicMultiChannelPerlin and set amplitude from trauma.
lerp(pos, target, const) per frame is frame-rate dependent — floatier at 30 FPS, snappier
at 144. Use 1 - exp(-rate*dt) or SmoothDamp.LateUpdate / after movement/physics resolve.SmoothDamp distance whip across the level.references/follow-and-framing.md.game-feel — owns screen-shake trauma/triggers; this skill exposes the offset it writes.godot-2d-movement, godot-3d-essentials — the player/world the camera frames; Camera3D setup.physics-tuning — interpolate camera follow with the physics step to kill jitter.platformer, fps-shooter — genres whose camera rules this skill implements.performance-optimization — cost of extra cameras, render targets, and split-screen.