Skip to content

M2.3 — Camera Follow

What you'll learn

  • Follow the player with a Camera2D and tune position smoothing.
  • Apply camera limits and zoom as deliberate design choices.
  • Keep the HUD still by placing it under UI while the camera moves World (the M1.4 split).

How it applies

  • The camera is the player's window into danger. In an ARPG the player must see incoming enemies and loot before they reach them. Zoom and follow behavior decide how much warning the player gets; too tight a zoom and off-screen attacks feel unfair, too wide and the character is a speck.
  • Smoothing is feel, not decoration. A camera rigidly glued to the player jitters with every sub-pixel movement and amplifies motion sickness; light smoothing lags the camera a hair behind, which reads as weight and polish. It is one boolean with an outsized effect on how the game feels.
  • Limits prevent showing the void. Clamping the camera to the arena bounds stops the player from scrolling past the level edge into empty space — a cheap setting that separates a finished-feeling game from a prototype.
  • The world/UI split pays off here. Because the camera affects only World (M1.4), the HUD under UI stays pinned automatically. If the HUD had been parented under World, it would now scroll off-screen with the camera — the bug this split was designed to prevent.

Concepts

Camera2D follows by being a child

A Camera2D defines the rectangle of the 2D world shown on screen, centered on the camera's global position. The simplest, most robust follow is to make the camera a child of the player: as the player moves, the camera moves with it, keeping the player centered. No follow code required — the scene tree does the work.

The camera must sit under World (directly or via the player, who is under World), not under UI. Anything under the UI CanvasLayer ignores the camera entirely; anything under World is framed by it. This is the concrete payoff of M1.4's split: world content scrolls, HUD content does not.

Smoothing

A camera rigidly pinned to the player snaps exactly to every position, including the tiny per-frame jitters of physics movement, which can look harsh. Position smoothing makes the camera chase the target with a slight lag instead of teleporting to it:

  • position_smoothing_enabled = true
  • position_smoothing_speed controls how quickly it catches up (higher = tighter, lower = floatier).

The effect: the camera trails the player by a fraction of a second, which reads as weight. Tune the speed to taste; very high values approach no smoothing, very low values feel like the camera is on a rubber band.

Example

Two cameras, same player. No smoothing: the view locks to the player's exact pixel each frame; on a pixel-art game with pixel snap (M1.1) it can look stuttery as the snapped position jumps a whole pixel at a time. Smoothing at a moderate speed: the camera interpolates toward the player, hiding the per-pixel jumps and adding a faint sense of momentum when the player changes direction. Same movement code; the boolean changes the feel.

Limits

Camera2D exposes limit_left/top/right/bottom in pixels. The camera will not scroll past these world coordinates, so the player can move to the arena edge without the view revealing the empty space beyond it. Set the limits to the bounds of the playable area (M8 builds a real arena; for now you can leave them at the defaults, which are effectively unbounded, or set generous values to experiment).

Limits also interact with stretch: with Aspect = keep (M1.1), the visible region is the base resolution; limits clamp where that region's edges can land in the world.

Zoom

Camera2D.zoom scales the view. Counterintuitively, a zoom greater than 1 zooms in (the world appears larger / you see less of it), and less than 1 zooms out. A top-down ARPG typically zooms in somewhat so the character and nearby enemies are readable, trading away peripheral awareness. Like speed in M2.1, zoom is a tuning knob worth exposing or at least centralizing, because it changes the game's readability and difficulty (more zoom = less warning of off-screen threats).

Walkthrough

  1. Open res://scenes/actor/player.tscn.
  2. Add a child of Player: Camera2D, renamed Camera.
  3. In the Inspector for Camera:
  4. Enable Position Smoothing → Enabled.
  5. Set Position Smoothing → Speed to a moderate value (try 58) and adjust after testing.
  6. Optionally set a Zoom of (1.5, 1.5) to zoom in; adjust to taste.
  7. Leave Limit at defaults for now (the arena arrives in M8); note where these are for later.
  8. Confirm Camera is Enabled (a Camera2D becomes the active camera when it enters the tree if it is the only/first one; with several, the enabled/make_current rules apply).
  9. Save the player scene. Open main.tscn — the player (with its camera) is already under World from M2.1.
  10. Press F5. Walk around: the player stays centered and the (currently empty) world scrolls beneath. There is nothing else to see yet, but you can confirm smoothing by changing direction sharply and watching the camera ease rather than snap.

Optional sanity check

Temporarily add a Sprite2D with a large texture somewhere under World (not under the player), at a fixed position. Walk away from it and confirm it scrolls off the edge of the screen as the camera follows you — proof the camera frames World. Then (mentally or with a temporary Label under the UI CanvasLayer) note that a HUD element would stay fixed instead. Remove the temporary sprite.

Self-check quiz

Q1 — What is the simplest robust way to make the camera follow the player?

A. Write follow code in _process that lerps the camera toward the player each frame. B. Make Camera2D a child of the player so it inherits the player's movement via the scene tree. C. Parent the camera under the UI CanvasLayer. D. Set the camera's position equal to the mouse each frame.

Reveal answer

B. Parenting the camera to the player makes the scene tree do the following for free — no code, no drift. A works but is unnecessary code for the common case (and is what smoothing already does internally). C is the bug: under CanvasLayer the camera and anything there ignore world framing, and the camera wouldn't track world content. D is a different mechanic (mouse-look), not player-follow.

Q2 — A Camera2D has zoom = (2, 2). What does the player see compared to zoom = (1, 1)?

A. The world appears smaller; more of it is visible (zoomed out). B. The world appears larger; less of it is visible (zoomed in). C. No change; zoom only affects 3D cameras. D. The HUD scales but the world does not.

Reveal answer

B. For Camera2D, zoom greater than 1 magnifies the world (zoom in), showing less area. This trips people up because it is the opposite of some engines' conventions. C is false. D is false — zoom affects the world view; the HUD under CanvasLayer is unaffected by the camera entirely.

Q3 — Why does enabling position smoothing improve feel on a pixel-snapped game specifically?

A. Smoothing increases the frame rate. B. With pixel snap, the player's snapped position jumps a whole pixel at a time; smoothing interpolates the camera between those jumps so the view doesn't stutter. C. Smoothing disables pixel snap. D. Smoothing is required for Camera2D to function.

Reveal answer

B. Pixel snap (M1.1) quantizes positions to whole pixels, which can make a hard-locked camera visibly step; smoothing eases the camera between those discrete positions, hiding the steps. A is false. C is false (they coexist; the snap applies to nodes, the smoothing to the camera's chase). D is false — a camera works without smoothing; smoothing is a feel improvement.

Integration question

Q4 — open

The camera you just added is a child of the player, which is a child of World; the HUD you build in later chapters is a child of UI (a CanvasLayer). Using the chain from keypress to screen, explain why the player's health bar will stay fixed on screen while the dungeon scrolls, and what single change to the scene tree would break that — turning the health bar into something that slides away.

Reveal expected answer

The camera frames everything under World: when the player moves, the camera (its child) moves, so all World content — tilemap, enemies, loot — shifts on screen to keep the player centered. The UI node is a CanvasLayer (M1.4), which renders in its own screen space and is not framed by any Camera2D; a health bar parented under UI therefore draws at fixed screen coordinates regardless of camera movement. The single change that breaks it is re-parenting the health bar (or the whole HUD) from UI to World — once under the camera-framed branch, it would scroll with the dungeon and slide off the edge as the player walks. The world/UI split exists precisely to make that mistake structural and obvious rather than a subtle per-element bug.