Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Guidance for using the optional Phantom Camera Godot addon in generated projects. Use when a Godot 4 game explicitly opts into Phantom Camera, already has the addon installed, or needs camera behavior that is cleaner with multiple virtual cameras than with a hand-written Camera2D script. Do not require this addon for ordinary projects that can be served by Godot's built-in Camera2D/Camera3D.
.claude/skills/randallliuxin-phantom-camera/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 45% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 95% | 0% |
| case-08 | ✗→✓ | ▲ Improved | -7% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 70% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 19% | 0% |
$ARGUMENTS
Phantom Camera is an optional Godot 4 addon for advanced camera behavior inspired by Cinemachine. Prefer it when the game needs several camera rigs, smooth transitions, framed/dead-zone following, group/path following, camera shake, or cutscene-style camera moves. Keep using plain Camera2D / Camera3D when the request is a simple static camera, a one-off follow script, or when the generated project has not opted into optional addons.
Use Phantom Camera only when one of these is true:
addons/phantom_camera/plugin.cfg exists and the plugin is enabled inproject.godot.
project config allows adding optional addons.
Do not silently add the addon to every scaffolded project. If the addon is not present, document the dependency and either ask for approval before installing it or implement a built-in camera fallback.
Compatibility note: before installing, check the selected Phantom Camera release or addons/phantom_camera/plugin.cfg for its supported Godot version. If the project is pinned below the addon's minimum, use built-in camera code instead.
For 2D, use this baseline scene shape:
textWorld |-- Player |-- Camera2D | `-- PhantomCameraHost |-- PhantomCamera2DFollow `-- PhantomCamera2DZone
For 3D, mirror the same structure with Camera3D, PhantomCameraHost, and PhantomCamera3D.
The PhantomCameraHost should be a child of the real Camera2D / Camera3D. Each PhantomCamera2D or PhantomCamera3D stores a camera behavior. The active camera is selected by priority: raise one PCam's priority to activate it and lower it when leaving that context.
Use a follow PCam for a player or other primary target. Glued follow is the lightest mode. Framed follow is better for platformers and arena games because the dead zone prevents the camera from drifting on every small movement.
gdscriptextends Node2D @onready var player: Node2D = $Player @onready var follow_pcam: PhantomCamera2D = $PhantomCamera2DFollow func _ready() -> void: follow_pcam.set_follow_target(player) follow_pcam.set_follow_damping(true) follow_pcam.set_follow_damping_value(Vector2(0.2, 0.2)) follow_pcam.set_tween_duration(0.35) follow_pcam.set_priority(20)
When the follow target is a physics body and the project runs on Godot 4.4 or newer, enable Physics Interpolation in Project Settings to reduce jitter. On older projects, follow a visual child that updates in _process instead of the physics body itself.
Use camera zones when an area of the map needs different zoom, framing, or camera limits.
textCameraZone2D |-- CollisionShape2D `-- PhantomCamera2DZone
gdscriptextends Area2D @export var active_priority := 40 @export var inactive_priority := 0 @onready var zone_pcam: PhantomCamera2D = $PhantomCamera2DZone func _on_body_entered(body: Node2D) -> void: if body.is_in_group("player"): zone_pcam.set_priority(active_priority) func _on_body_exited(body: Node2D) -> void: if body.is_in_group("player"): zone_pcam.set_priority(inactive_priority)
Keep the default follow PCam at a lower priority, such as 20, so entering a zone cleanly takes over and exiting returns control to the normal camera.
Set per-PCam tween settings instead of writing custom interpolation between cameras. Use short durations for gameplay contexts and longer durations for cinematic reveals.
gdscriptfunc focus_boss_intro() -> void: $PhantomCamera2DPlayer.set_priority(10) $PhantomCamera2DBoss.set_tween_duration(1.0) $PhantomCamera2DBoss.set_priority(50) func return_to_player() -> void: $PhantomCamera2DBoss.set_priority(0) $PhantomCamera2DPlayer.set_priority(20)
If several PCams should share the same curve and duration, create one PhantomCameraTween resource and assign it to each PCam with set_tween_resource().
Use Phantom Camera noise for sustained shake tied to a PCam, such as a storm, rumbling machinery, or low-health effect. Use a noise emitter for one-shot events such as explosions or heavy impacts when the addon is present.
gdscript@onready var follow_pcam: PhantomCamera2D = $PhantomCamera2DFollow func start_danger_shake(noise: PhantomCameraNoise2D) -> void: follow_pcam.set_noise(noise) func stop_danger_shake() -> void: follow_pcam.set_noise(null)
Do not stack several continuous noise sources on the same PCam unless the design really needs it. Keep gameplay readability ahead of spectacle.
For cutscenes, create dedicated PCams at named positions and switch priority as the sequence advances. This keeps camera logic visible in the scene tree and avoids mixing story timing into the player controller.
gdscriptextends Node @onready var player_pcam: PhantomCamera2D = %PhantomCamera2DPlayer @onready var gate_pcam: PhantomCamera2D = %PhantomCamera2DGateReveal func play_gate_reveal() -> void: player_pcam.set_priority(10) gate_pcam.set_tween_duration(1.25) gate_pcam.set_priority(60) await gate_pcam.tween_completed await get_tree().create_timer(0.8).timeout gate_pcam.set_priority(0) player_pcam.set_priority(20)
For longer authored sequences, drive PCam priority, target positions, and zoom from AnimationPlayer or Phantom Camera's tween director rather than writing a large procedural timeline.
When a generated game opts into Phantom Camera, include a small camera test pass that exercises the common camera designs the game actually uses. This can be a manual smoke test scene, a documented playtest route, or an automated gameplay test when the project already has an end-to-end runner.
Cover these scenarios when they are relevant to the genre:
| Scenario | What to verify | |----------|----------------| | Player follow | The camera tracks the player without jitter, keeps the player readable, and does not overshoot during starts, stops, jumps, or direction changes. | | Camera zone | Entering a trigger raises the zone PCam priority, applies the intended zoom/framing/limits, and leaving the zone returns to the default follow camera. | | Transition | Switching between gameplay, boss, room, or reveal PCams uses the intended tween duration and curve without a visible snap. | | Shake | Shake is strong enough to sell the impact but does not hide hazards, UI, or player position; temporary shake stops after the event. | | Cutscene move | Control returns to the gameplay PCam after the authored camera beat, even if the cutscene is skipped or interrupted. |
For platformers, test vertical movement, ledges, and fast horizontal reversals. For top-down games, test room boundaries, diagonal motion, and camera zones at doorways. For combat games, test hit shake, boss reveals, and crowded arenas. For puzzle or exploration games, test static room framing and slow reveal moves.
Do not add a showcase scene that is disconnected from the actual game design. The pass should confirm the camera behavior the player will experience in normal play.
Before finishing a generated project that uses Phantom Camera:
addons/phantom_camera/plugin.cfg exists and the plugin is enabled.Camera2D or Camera3D per viewport.PhantomCameraHost is below that real camera.enters a zone, triggers a transition, and returns to the player camera.
follow, zone, shake, or cutscene behavior.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 16,458 | 11,223 | -32% | 1 | 1 | 0% | 2,899 | 4,204 | +45% | 0 | 0 | — |
case-02 | fail→fail | 19,503 | 16,236 | -17% | 1 | 1 | 0% | 3,259 | 4,795 | +47% | 0 | 0 | — |
case-03 | pass→pass | 9,844 | 10,165 | +3% | 1 | 1 | 0% | 1,634 | 3,653 | +124% | 0 | 0 | — |
case-04 | pass→pass | 17,562 | 10,682 | -39% | 1 | 1 | 0% | 2,866 | 4,015 | +40% | 0 | 0 | — |
case-05 | fail→pass | 9,126 | 6,210 | -32% | 1 | 1 | 0% | 1,691 | 3,301 | +95% | 0 | 0 | — |
case-06 | fail→fail | 11,895 | 6,592 | -45% | 1 | 1 | 0% | 2,186 | 3,405 | +56% | 0 | 0 | — |
case-07 | pass→pass | 18,309 | 13,559 | -26% | 1 | 1 | 0% | 2,979 | 4,378 | +47% | 0 | 0 | — |
case-08 | fail→pass | 22,293 | 8,784 | -61% | 1 | 1 | 0% | 3,928 | 3,652 | -7% | 0 | 0 | — |
case-09 | fail→pass | 10,956 | 7,091 | -35% | 1 | 1 | 0% | 1,943 | 3,312 | +70% | 0 | 0 | — |
case-19 | pass→pass | 9,449 | 6,928 | -27% | 1 | 1 | 0% | 1,861 | 3,262 | +75% | 0 | 0 | — |
case-10 | fail→pass | 14,967 | 5,952 | -60% | 1 | 1 | 0% | 2,615 | 3,120 | +19% | 0 | 0 | — |
case-11 | fail→pass | 14,808 | 6,184 | -58% | 1 | 1 | 0% | 2,600 | 3,104 | +19% | 0 | 0 | — |
case-12 | pass→pass | 7,799 | 3,449 | -56% | 1 | 1 | 0% | 1,149 | 2,540 | +121% | 0 | 0 | — |
case-13 | fail→pass | 12,013 | 8,182 | -32% | 1 | 1 | 0% | 1,836 | 3,364 | +83% | 0 | 0 | — |
case-20 | pass→pass | 11,358 | 12,266 | +8% | 1 | 1 | 0% | 1,765 | 4,071 | +131% | 0 | 0 | — |
case-14 | fail→pass | 13,588 | 5,095 | -63% | 1 | 1 | 0% | 2,439 | 3,008 | +23% | 0 | 0 | — |
case-15 | pass→pass | 17,830 | 14,895 | -16% | 1 | 1 | 0% | 2,658 | 4,385 | +65% | 0 | 0 | — |
case-16 | pass→pass | 20,178 | 17,053 | -15% | 1 | 1 | 0% | 2,881 | 4,470 | +55% | 0 | 0 | — |
case-17 | fail→pass | 21,711 | 13,048 | -40% | 1 | 1 | 0% | 2,974 | 3,870 | +30% | 0 | 0 | — |
case-18 | pass→pass | 12,559 | 7,446 | -41% | 1 | 1 | 0% | 1,861 | 3,108 | +67% | 0 | 0 | — |
case-21 | pass→pass | 14,490 | 13,529 | -7% | 1 | 1 | 0% | 2,450 | 4,287 | +75% | 0 | 0 | — |
case-22 | fail→pass | 18,756 | 8,562 | -54% | 1 | 1 | 0% | 2,619 | 3,542 | +35% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +45 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.