~2 min read

Camera and Phantom Camera

Camera3D, first/third-person views, and a plugin equivalent of Cinemachine.

Camera3D — the main node

Camera3D is the node that “looks” into the scene. Only one Camera3D is active at any moment (if you’re not using separate viewports). A scene can have several cameras; the active one is the one whose current = true, or the one made active via make_current().

Key properties:

  • projectionPERSPECTIVE (3D, with perspective) or ORTHOGONAL (without, for isometric/2.5D).
  • fov — field of view in degrees, vertical. The FPS standard is 60–90°.
  • near, far — clipping planes. A large spread hurts Z-buffer precision (Z-fighting).
  • cull_mask — a bitmask: which visual layers this camera renders.
@onready var camera: Camera3D = $Head/Camera3D

func _ready() -> void:
    camera.fov = 75.0
    camera.near = 0.05
    camera.far = 500.0

Simple follow

The equivalent of Unity’s SmoothDamp approach. Godot has no built-in SmoothDamp, but it has lerp:

extends Camera3D

@export var target: Node3D
@export var offset: Vector3 = Vector3(0, 2.0, 4.0)  # behind, higher up
@export var smoothing: float = 5.0

func _process(delta: float) -> void:
    if target == null:
        return
    var desired = target.global_position + target.global_transform.basis * offset
    global_position = global_position.lerp(desired, smoothing * delta)
    look_at(target.global_position + Vector3.UP * 1.5, Vector3.UP)

vector.lerp(to, weight) is a Vector3 method for linear interpolation. look_at(target, up_vector) aims the camera’s “forward” (−Z) at the target.

Camera follow goes in _process, not _physics_process

If you move the camera in _physics_process, it will “stick” on frames between physics ticks and look jittery. The camera is a render task; its place is in _process. If the player is a CharacterBody3D with physics, interpolate its position or enable global physics interpolation in Project Settings (since 4.3).

Phantom Camera — a Cinemachine equivalent

Godot core has nothing on the level of Unity’s Cinemachine. But there’s Phantom Camera — a popular community plugin (ramokz/phantom-camera in the Asset Store / Godot Asset Library). It provides virtual cameras with blends, follow / look-at modes, framing, dead zones, and hosts.

Installation: AssetLib (inside the editor) → search for “Phantom Camera” → Install. Enable it in Project → Project Settings → Plugins.

A basic follow-camera in Phantom Camera

Player                            ← CharacterBody3D
└── PhantomCamera3D_Follow        ← virtual camera (follow_mode = ThirdPerson)

PhantomCameraHost                 ← one per scene, attached to the main Camera3D
└── Camera3D                      ← the real camera

Virtual camera parameters:

  • Follow ModeSimple, Group, Path, Framed, ThirdPerson.
  • Look At ModeNone, Simple, Group, Mimic.
  • Tween Resource — the curve and duration of the blend when switching.
  • Priority — priority; PhantomCameraHost automatically picks the camera with the highest one.
# Switching between virtual cameras
$PhantomCamera3D_FirstPerson.priority = 20
$PhantomCamera3D_ThirdPerson.priority = 10
# The host smoothly transitions to FirstPerson
Phantom Camera is a must-have for serious 3D projects

If your game needs cutscenes, first/third-person view switching, a drone camera, or fine framing — install the plugin from the very start. Writing all of this by hand is slow and fragile.

Rotating the camera with the mouse (FPS)

We already saw this in the input chapter, but let’s revisit it in the camera context:

extends CharacterBody3D

@onready var head: Node3D = $Head
@onready var camera: Camera3D = $Head/Camera3D

@export var sensitivity: float = 0.003
const MAX_PITCH: float = 1.4

func _ready() -> void:
    Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

func _unhandled_input(event: InputEvent) -> void:
    if event is InputEventMouseMotion:
        # yaw — rotates the player's body (affects movement direction)
        rotate_y(-event.relative.x * sensitivity)
        # pitch — head only, the body doesn't tilt
        head.rotate_x(-event.relative.y * sensitivity)
        head.rotation.x = clamp(head.rotation.x, -MAX_PITCH, MAX_PITCH)

Hierarchy:

Player (CharacterBody3D)        ← rotate_y (yaw)
├── CollisionShape3D
└── Head (Node3D)                ← rotate_x (pitch)
    └── Camera3D                  ← position (0, 0, 0)

Pitch (vertical) is applied to the Head so that the player’s collider doesn’t tilt when you look up at the sky.

Multiple viewports — split screen, mini-maps

For split-screen or picture-in-picture, use SubViewport + SubViewportContainer:

SubViewportContainer (stretches across half the screen)
└── SubViewport
    └── Camera3D (current = true)
    └── (the same 3D world, but via a shared World3D)

SubViewport.world_3d = main_viewport.world_3d — both cameras render the same scene.

Projection types

Camera3D.projection affects the feel:

  • PERSPECTIVE — natural 3D where distant objects shrink.
  • ORTHOGONAL — no perspective. The size property sets the width of the visible area. Suitable for isometric views, technical visualizations, and 2.5D.
  • FRUSTUM — an advanced asymmetric projection (off-axis). Rarely needed — VR, special effects.

In the next chapter — rendering.