camera-systems

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Camera systems

相机系统

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 to use

使用场景

  • Use when a 2D camera should follow the player smoothly, stay inside the level, lead the player's motion, or ignore small movements (deadzone).
  • Use when building a 3D third-person orbit camera (mouse/stick look, collision push-in) or a first-person look controller, or framing multiple targets at once.
  • Use to fix camera jitter, snapping, motion sickness, or a camera that shows past the level edge.
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
.
  • 当2D相机需要平滑跟随玩家、保持在关卡范围内、预判玩家移动方向,或是忽略微小移动(死区)时使用。
  • 当构建3D第三人称环绕相机(鼠标/摇杆视角、碰撞自动拉近)、第一人称视角控制器,或是需要同时取景多个目标时使用。
  • 用于修复相机抖动、突然跳转、晕动症,或是相机显示超出关卡边缘的问题。
不适用场景: 若需设置屏幕震动的幅度和触发条件,请使用
game-feel
技能(本技能仅提供其驱动的震动偏移钩子)。若需设置引擎具体的相机节点/组件,请使用
godot-3d-essentials
(Camera3D、环境设置)或对应引擎的技能。玩家移动相关内容请使用引擎移动技能(如
godot-2d-movement
)。多相机/渲染目标的性能优化请参考
performance-optimization

Core workflow

核心工作流程

  1. Decide what the camera serves. Platformer (lead the jump, see hazards), top-down (center with deadzone), third-person (orbit + collision), first-person (look only). The genre sets the rules.
  2. Follow smoothly and frame-rate independently. Move the camera toward the target with exponential smoothing or a spring (
    SmoothDamp
    ), not a fixed
    lerp(a, b, 0.1)
    — that 0.1 is per-frame and changes with frame rate.
  3. Add a deadzone so tiny target movements don't nudge the camera; it only follows once the target leaves a box/zone. Stops nausea in twitchy games.
  4. Lead the action with look-ahead by offsetting the camera target in the direction of motion or facing, eased in/out so it doesn't whip.
  5. Clamp to level bounds so the camera never shows outside the playable area; combine with smoothing so it eases to a stop at the edge.
  6. For 3D, separate look from collision. Orbit via yaw/pitch on a rig; use a spring arm / ray to pull the camera in when geometry blocks it; clamp pitch.
  7. Update the camera after the target moves. Follow in the late/post step (after movement and physics resolve) to avoid a one-frame lag jitter.
  8. Verify by moving the target at low and high frame rates, into corners and walls, and at the level edges; confirm no jitter, no peeking past bounds, smooth stops. Report what you saw.
  1. 明确相机的服务目标:平台跳跃游戏(预判跳跃、显示危险)、俯视角游戏(带死区居中)、第三人称游戏(环绕+碰撞)、第一人称游戏(仅视角控制)。游戏类型决定相机规则。
  2. 平滑且帧率无关的跟随:使用指数平滑或弹簧效果(
    SmoothDamp
    )让相机向目标移动,而非固定的
    lerp(a, b, 0.1)
    ——这个0.1是每帧的参数,会随帧率变化。
  3. 添加死区:让微小的目标移动不会触发相机跟随;只有当目标离开特定区域时,相机才会跟随。可避免快节奏游戏中的晕动症。
  4. 通过前瞻预判动作:根据目标的移动方向或朝向偏移相机目标点,加入缓动效果避免镜头突然晃动。
  5. 限制关卡边界:确保相机不会显示可玩区域外的内容;结合平滑效果让相机在边缘处平缓停止。
  6. 3D场景分离视角与碰撞:通过摇轴/俯仰控制环绕装置;使用弹簧臂/射线在几何体阻挡时拉近相机;限制俯仰角度。
  7. 目标移动后更新相机:在后期/更新步骤(移动和物理计算完成后)执行跟随逻辑,避免一帧延迟导致的抖动。
  8. 多场景验证:在高低帧率下移动目标,测试角落、墙壁和关卡边缘场景;确认无抖动、无边界外显示、停止平滑。记录测试结果。

Patterns

实现模式

1. Godot 2D built-in follow: smoothing + bounds (don't hand-roll first)

1. Godot 2D内置跟随:平滑+边界(优先使用内置功能,勿手动实现)

gdscript
undefined
gdscript
undefined

Godot 4.x Camera2D. Engine-provided smoothing + hard limits + drag margins.

Godot 4.x 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
undefined
@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
undefined

2. Frame-rate-independent smooth follow (when you hand-roll it)

2. 帧率无关的平滑跟随(需手动实现时)

gdscript
undefined
gdscript
undefined

RIGHT: exponential smoothing — same feel at any FPS.
rate
~ 5..12.

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)
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)

WRONG: global_position = global_position.lerp(target.global_position, 0.1)

→ faster smoothing at higher FPS; different feel on every machine.

→ faster smoothing at higher FPS; different feel on every machine.

Unity 6: Vector3.SmoothDamp(transform.position, target.position, ref vel, smoothTime) in

Unity 6: Vector3.SmoothDamp(transform.position, target.position, ref vel, smoothTime) in

LateUpdate gives the same spring behavior with built-in frame-rate correction.

LateUpdate gives the same spring behavior with built-in frame-rate correction.

undefined
undefined

3. Deadzone + look-ahead (lead the player, ignore jitter)

3. 死区+前瞻(预判玩家动作,忽略微小抖动)

gdscript
undefined
gdscript
undefined

Camera only chases once the target leaves the deadzone box, then aims AHEAD of motion.

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
undefined
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
undefined

4. 3D third-person orbit with collision push-in

4. 带碰撞自动拉近的3D第三人称环绕相机

gdscript
undefined
gdscript
undefined

Godot 4.x. Yaw/pitch a pivot; a SpringArm3D auto-pulls the camera in when blocked.

Godot 4.x. 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.
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: a Cinemachine 3 CinemachineCamera (namespace Unity.Cinemachine) with an Orbital

Unity 6: a Cinemachine 3 CinemachineCamera (namespace Unity.Cinemachine) with an Orbital

Follow + Cinemachine Deoccluder; the CinemachineBrain on the Camera blends automatically.

Follow + Cinemachine Deoccluder; the CinemachineBrain on the Camera blends automatically.

undefined
undefined

5. Screen shake hook (owned trigger lives in
game-feel
)

5. 屏幕震动钩子(触发逻辑归属于
game-feel

gdscript
undefined
gdscript
undefined

Expose an additive offset the game-feel trauma model writes to; follow + shake compose.

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
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.

Unity Cinemachine: add a CinemachineBasicMultiChannelPerlin and set amplitude from trauma.

undefined
undefined

Pitfalls

常见陷阱

  • lerp(pos, target, const)
    per frame
    is frame-rate dependent — floatier at 30 FPS, snappier at 144. Use
    1 - exp(-rate*dt)
    or
    SmoothDamp
    .
  • Following in the normal update before the target has moved yields a one-frame lag jitter. Follow in
    LateUpdate
    / after movement/physics resolve.
  • No bounds clamp lets the camera show black past the level edge. Clamp focus to the level rect (account for the viewport half-size so the view, not the center, stays inside).
  • No deadzone in twitchy games makes the camera twitch with every micro-movement → nausea.
  • Unclamped pitch in third/first-person flips the camera over the top. Clamp pitch to ~±80°.
  • Camera clipping through walls in 3D — use a spring arm / occlusion ray to pull in.
  • Snapping on teleport/respawn is jarring; either hard-cut intentionally (and reset smoothing) or fast-ease. Don't let a huge
    SmoothDamp
    distance whip across the level.
  • Shake driving the follow target instead of an additive offset makes follow fight shake. Compose: smooth follow first, add shake offset last.
  • Per-axis vs radial deadzone confusion — a box deadzone feels different from a circular one; pick deliberately.
  • 每帧使用
    lerp(pos, target, const)
    :依赖帧率——30 FPS下更平缓,144 FPS下更灵敏。请使用
    1 - exp(-rate*dt)
    SmoothDamp
  • 在目标移动前的常规更新中执行跟随:会导致一帧延迟的抖动。请在
    LateUpdate
    /移动和物理计算完成后执行跟随逻辑。
  • 无边界限制:会让相机显示关卡外的黑屏区域。请将焦点限制在关卡范围内(需考虑视口半尺寸,确保画面而非中心点保持在关卡内)。
  • 快节奏游戏中未设置死区:相机随每一次微小移动抖动→引发晕动症。
  • 第三人称/第一人称视角未限制俯仰角度:镜头会翻转过顶。请将俯仰角度限制在±80°左右。
  • 3D场景中相机穿墙:请使用弹簧臂/遮挡射线拉近相机。
  • 传送/重生时镜头突然跳转:体验糟糕;要么故意硬切(并重置平滑效果),要么快速缓动。不要让
    SmoothDamp
    的大距离导致镜头快速横穿场景。
  • 震动驱动跟随目标而非叠加偏移:会让跟随逻辑与震动冲突。应组合使用:先实现平滑跟随,最后添加震动偏移。
  • 混淆轴向死区与径向死区:矩形死区与圆形死区手感不同;需刻意选择。

References

参考资料

  • For the exponential-smoothing/spring derivation, a complete deadzone+look-ahead+bounds 2D rig, 3D spring-arm/orbit details, first-person look, multi-target/group framing and split-screen, cinematic camera blends, and the Cinemachine 3 / Godot Camera2D / PhantomCamera mapping, read
    references/follow-and-framing.md
    .
  • 若需了解指数平滑/弹簧效果推导、完整的死区+前瞻+边界2D控制装置、3D弹簧臂/环绕细节、第一人称视角、多目标/群体取景与分屏、电影级相机过渡,以及Cinemachine 3 / Godot Camera2D / PhantomCamera的映射,请阅读
    references/follow-and-framing.md

Related skills

相关技能

  • 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.
  • game-feel
    ——负责屏幕震动的创伤值/触发逻辑;本技能提供其写入的偏移量。
  • godot-2d-movement
    godot-3d-essentials
    ——相机取景的玩家/世界对象;Camera3D设置。
  • physics-tuning
    ——结合物理步骤插值相机跟随以消除抖动。
  • platformer
    fps-shooter
    ——本技能实现其相机规则的游戏类型。
  • performance-optimization
    ——多相机、渲染目标和分屏的性能开销。