[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6KR7EEF28STCSTCWFAE5FMV":3,"skill-same:01M6KR7EEF28STCSTCWFAE5FMV":76},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":30,"linkOnly":31,"markdownContent":32,"markdownBody":33,"frontMatter":34,"refName":35,"commitSha":36,"skillDirectory":37,"forksCount":38,"repositoryHtmlUrl":39,"repositoryHomepageUrl":6,"ownerHtmlUrl":40,"ownerBlogUrl":41,"ownerDescription":42,"updatedAt":43,"install":44,"resources":57},"01M6KR7EEF28STCSTCWFAE5FMV","camera-system",null,"Use when implementing cameras — smooth follow, screen shake, camera zones, and transitions for 2D and 3D","jame581\u002FGodotPrompter","jame581","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F21112138?v=4","01M6KR3KEQ9Z30WVJRR2E2ZVXE","Jan Mesarč","01M6KR3KJVRMPDSMASQPBCBVK3",957,"MIT","https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Fcamera-system\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29],"system","camera system","camera","3D","for","implementing","2d","use","transitions","screen","when","2026-11-10 10:12:30",false,"---\nname: camera-system\ndescription: Use when implementing cameras — smooth follow, screen shake, camera zones, and transitions for 2D and 3D\n---\n\n# Camera Systems in Godot 4.3+\n\nAll examples target Godot 4.3+ with no deprecated APIs. GDScript is shown first, then C#.\n\n> **Related skills:** **player-controller** for first-person camera setup, **state-machine** for camera state transitions, **godot-optimization** for camera culling and performance, **physics-system** for physics interpolation and camera smoothing, **2d-essentials** for canvas layers, parallax scrolling, and coordinate conversion, **math-essentials** for smoothstep and lerp-based interpolation, **tween-animation** for camera shake and cinematic transitions, **phantom-camera** for a Cinemachine-style camera addon.\n\n---\n\n## 1. Camera2D Basics\n\n### Key Properties\n\n| Property | Type | Description |\n|---|---|---|\n| `position_smoothing_enabled` | `bool` | Enables built-in position smoothing (lerp toward target) |\n| `position_smoothing_speed` | `float` | Speed of built-in smoothing (default `5.0`) |\n| `drag_horizontal_enabled` | `bool` | Enables a horizontal drag zone; camera only moves when target exits zone |\n| `drag_vertical_enabled` | `bool` | Enables a vertical drag zone |\n| `limit_left` | `int` | Left pixel boundary — camera will not scroll past this |\n| `limit_right` | `int` | Right pixel boundary |\n| `limit_top` | `int` | Top pixel boundary |\n| `limit_bottom` | `int` | Bottom pixel boundary |\n| `zoom` | `Vector2` | Zoom level; `Vector2(2, 2)` = 2× zoom in, `Vector2(0.5, 0.5)` = zoom out |\n\nSet limits to match your tilemap or level bounds so the camera never shows outside the world. Limits are in world pixels, not tiles.\n\n---\n\n## 2. Smooth Follow\n\nA manual follow camera gives more control than the built-in smoothing — you can add look-ahead, offset, and custom easing.\n\n### GDScript\n\n```gdscript\nextends Camera2D\n\n## Target node to follow (assign in Inspector or via code)\n@export var target: Node2D\n\n## How quickly the camera catches up to the target (higher = snappier)\n@export var follow_speed: float = 8.0\n\n## How far ahead the camera leads in the movement direction\n@export var look_ahead_distance: float = 80.0\n\n## How quickly the look-ahead offset responds to direction changes\n@export var look_ahead_speed: float = 4.0\n\nvar _look_ahead_offset: Vector2 = Vector2.ZERO\nvar _previous_target_pos: Vector2 = Vector2.ZERO\n\nfunc _ready() -> void:\n    # Disable built-in smoothing — we handle it manually\n    position_smoothing_enabled = false\n    if target:\n        _previous_target_pos = target.global_position\n        global_position = target.global_position\n\nfunc _process(delta: float) -> void:\n    if not target:\n        return\n\n    # Compute movement direction from last frame\n    var move_delta: Vector2 = target.global_position - _previous_target_pos\n    _previous_target_pos = target.global_position\n\n    # Smoothly steer look-ahead offset toward movement direction\n    var desired_ahead: Vector2 = move_delta.normalized() * look_ahead_distance if move_delta.length() > 0.5 else Vector2.ZERO\n    _look_ahead_offset = _look_ahead_offset.lerp(desired_ahead, look_ahead_speed * delta)\n\n    # Lerp camera position toward target + look-ahead\n    var desired_pos: Vector2 = target.global_position + _look_ahead_offset\n    global_position = global_position.lerp(desired_pos, follow_speed * delta)\n```\n\n### C#\n\n```csharp\nusing Godot;\n\npublic partial class SmoothFollowCamera : Camera2D\n{\n    [Export] public Node2D Target { get; set; }\n    [Export] public float FollowSpeed { get; set; } = 8.0f;\n    [Export] public float LookAheadDistance { get; set; } = 80.0f;\n    [Export] public float LookAheadSpeed { get; set; } = 4.0f;\n\n    private Vector2 _lookAheadOffset = Vector2.Zero;\n    private Vector2 _previousTargetPos = Vector2.Zero;\n\n    public override void _Ready()\n    {\n        PositionSmoothingEnabled = false;\n        if (Target != null)\n        {\n            _previousTargetPos = Target.GlobalPosition;\n            GlobalPosition = Target.GlobalPosition;\n        }\n    }\n\n    public override void _Process(double delta)\n    {\n        if (Target == null)\n            return;\n\n        float dt = (float)delta;\n\n        Vector2 moveDelta = Target.GlobalPosition - _previousTargetPos;\n        _previousTargetPos = Target.GlobalPosition;\n\n        Vector2 desiredAhead = moveDelta.Length() > 0.5f\n            ? moveDelta.Normalized() * LookAheadDistance\n            : Vector2.Zero;\n\n        _lookAheadOffset = _lookAheadOffset.Lerp(desiredAhead, LookAheadSpeed * dt);\n\n        Vector2 desiredPos = Target.GlobalPosition + _lookAheadOffset;\n        GlobalPosition = GlobalPosition.Lerp(desiredPos, FollowSpeed * dt);\n    }\n}\n```\n\n---\n\n## 3. Screen Shake\n\nA trauma-based system produces more natural-looking shake than a simple sine wave. High trauma = violent shake; trauma decays over time; offset scales with `trauma^2` so small trauma values feel subtle.\n\n### GDScript\n\n```gdscript\nclass_name ScreenShakeCamera\nextends Camera2D\n\n## Maximum pixel offset during maximum trauma\n@export var max_offset: Vector2 = Vector2(20.0, 15.0)\n\n## Maximum rotation offset in degrees during maximum trauma\n@export var max_roll: float = 3.0\n\n## Rate at which trauma decays per second (0–1 range)\n@export var decay_rate: float = 1.5\n\nvar _trauma: float = 0.0  # 0.0 = no shake, 1.0 = maximum shake\n\n# Optional: use noise for smooth, organic shake\nvar _noise: FastNoiseLite\nvar _noise_time: float = 0.0\n@export var use_noise: bool = true\n@export var noise_speed: float = 60.0\n\nfunc _ready() -> void:\n    ignore_rotation = false  # default is true (rotation ignored); the roll needs false, which also makes the view follow a rotating parent\n    _noise = FastNoiseLite.new()\n    _noise.noise_type = FastNoiseLite.TYPE_SIMPLEX\n    _noise.seed = randi()\n\n## Call this from any node to trigger a shake (amount in 0–1 range; can stack)\nfunc add_trauma(amount: float) -> void:\n    _trauma = minf(_trauma + amount, 1.0)\n\nfunc _process(delta: float) -> void:\n    if _trauma \u003C= 0.0:\n        offset = Vector2.ZERO\n        rotation = 0.0\n        return\n\n    # Decay trauma over time\n    _trauma = maxf(_trauma - decay_rate * delta, 0.0)\n    _noise_time += delta * noise_speed\n\n    var shake: float = _trauma * _trauma  # squaring gives subtle feel at low trauma\n\n    if use_noise:\n        offset.x = max_offset.x * shake * _noise.get_noise_2d(_noise_time, 0.0)\n        offset.y = max_offset.y * shake * _noise.get_noise_2d(0.0, _noise_time)\n        rotation = deg_to_rad(max_roll) * shake * _noise.get_noise_2d(_noise_time, _noise_time)\n    else:\n        offset.x = max_offset.x * shake * randf_range(-1.0, 1.0)\n        offset.y = max_offset.y * shake * randf_range(-1.0, 1.0)\n        rotation = deg_to_rad(max_roll) * shake * randf_range(-1.0, 1.0)\n```\n\n**Triggering shake from another node:**\n\n```gdscript\n# Any node that can reach the camera\nfunc on_explosion() -> void:\n    var cam := get_viewport().get_camera_2d() as ScreenShakeCamera\n    if cam:\n        cam.add_trauma(0.6)\n```\n\n### C#\n\n```csharp\nusing Godot;\n\npublic partial class ScreenShakeCamera : Camera2D\n{\n    [Export] public Vector2 MaxOffset { get; set; } = new Vector2(20f, 15f);\n    [Export] public float MaxRoll { get; set; } = 3.0f;\n    [Export] public float DecayRate { get; set; } = 1.5f;\n    [Export] public bool UseNoise { get; set; } = true;\n    [Export] public float NoiseSpeed { get; set; } = 60.0f;\n\n    private float _trauma = 0f;\n    private float _noiseTime = 0f;\n    private FastNoiseLite _noise;\n\n    public override void _Ready()\n    {\n        IgnoreRotation = false; \u002F\u002F default is true (rotation ignored); the roll needs false, which also makes the view follow a rotating parent\n        _noise = new FastNoiseLite();\n        _noise.NoiseType = FastNoiseLite.NoiseTypeEnum.Simplex;\n        _noise.Seed = (int)GD.Randi();\n    }\n\n    public void AddTrauma(float amount)\n    {\n        _trauma = Mathf.Min(_trauma + amount, 1.0f);\n    }\n\n    public override void _Process(double delta)\n    {\n        if (_trauma \u003C= 0f)\n        {\n            Offset = Vector2.Zero;\n            Rotation = 0f;\n            return;\n        }\n\n        float dt = (float)delta;\n        _trauma = Mathf.Max(_trauma - DecayRate * dt, 0f);\n        _noiseTime += dt * NoiseSpeed;\n\n        float shake = _trauma * _trauma;\n\n        if (UseNoise)\n        {\n            Offset = new Vector2(\n                MaxOffset.X * shake * _noise.GetNoise2D(_noiseTime, 0f),\n                MaxOffset.Y * shake * _noise.GetNoise2D(0f, _noiseTime)\n            );\n            Rotation = Mathf.DegToRad(MaxRoll) * shake * _noise.GetNoise2D(_noiseTime, _noiseTime);\n        }\n        else\n        {\n            Offset = new Vector2(\n                MaxOffset.X * shake * (float)GD.RandRange(-1.0, 1.0),\n                MaxOffset.Y * shake * (float)GD.RandRange(-1.0, 1.0)\n            );\n            Rotation = Mathf.DegToRad(MaxRoll) * shake * (float)GD.RandRange(-1.0, 1.0);\n        }\n    }\n}\n```\n\n---\n\n## 4. Camera Zones \u002F Rooms\n\nFor room-based games (metroidvanias, top-down dungeons): an `Area2D` per room with a script that, on `body_entered`, tweens the active `Camera2D`'s `limit_left` \u002F `limit_right` \u002F `limit_top` \u002F `limit_bottom` to the room's bounds. Smooth transitions when the player crosses room boundaries.\n\n> See [references\u002Fcamera-zones.md](references\u002Fcamera-zones.md) for the full GDScript + C# CameraZone implementation.\n\n---\n\n## 5. Camera3D Patterns\n\nThree canonical 3D camera setups: **third-person follow** with `SpringArm3D` (handles wall collision), **orbit camera** with mouse-drag rotation, **first-person** with mouse-look-from-camera.\n\n> See [references\u002Fcamera3d-patterns.md](references\u002Fcamera3d-patterns.md) for full GDScript implementations of each pattern.\n\n---\n\n## 6. Camera Transitions\n\nAsync camera transitions via `Tween` + `await` (`ToSignal` in C#). The pattern: tween the current camera's position and zoom (2D) or FOV (3D) to the next camera's values, then call `make_current()` on the next camera. Works for both 2D and 3D.\n\n> See [references\u002Ftransitions.md](references\u002Ftransitions.md) for the full `CameraTransitionManager` implementation (2D and 3D).\n\n---\n\n## 7. Split Screen (Local Multiplayer)\n\nRender multiple cameras to separate `SubViewport`s, then arrange `SubViewportContainer`s in a layout (`HBoxContainer`, `VBoxContainer`, or `GridContainer`). Each player's camera is set as `current` for its viewport.\n\n> See [references\u002Fsplit-screen.md](references\u002Fsplit-screen.md) for the SubViewport scene-tree setup and a 2-player split-screen example.\n\n---\n\n## 8. Implementation Checklist\n\n- [ ] `Camera2D` limits match level\u002Ftilemap bounds so no out-of-world edges are visible\n- [ ] Smooth follow uses `_process` (visual interpolation), not `_physics_process`\n- [ ] Screen shake resets `offset` and `rotation` to zero when `_trauma` reaches `0.0`\n- [ ] `add_trauma()` clamps to `1.0`; it does not exceed maximum shake\n- [ ] Camera zone `Area2D` collision layers are set so only the player triggers them\n- [ ] `SpringArm3D` collision mask includes all environment layers for wall avoidance\n- [ ] Camera transition awaits tween completion before calling `make_current()`\n- [ ] Split screen `SubViewport` sizes are updated on window resize (`get_tree().root.size_changed` signal)\n- [ ] Only one `SubViewport` has `audio_listener_enable_2d` or `audio_listener_enable_3d` set to `true`\n","# Camera Systems in Godot 4.3+\n\nAll examples target Godot 4.3+ with no deprecated APIs. GDScript is shown first, then C#.\n\n> **Related skills:** **player-controller** for first-person camera setup, **state-machine** for camera state transitions, **godot-optimization** for camera culling and performance, **physics-system** for physics interpolation and camera smoothing, **2d-essentials** for canvas layers, parallax scrolling, and coordinate conversion, **math-essentials** for smoothstep and lerp-based interpolation, **tween-animation** for camera shake and cinematic transitions, **phantom-camera** for a Cinemachine-style camera addon.\n\n---\n\n## 1. Camera2D Basics\n\n### Key Properties\n\n| Property | Type | Description |\n|---|---|---|\n| `position_smoothing_enabled` | `bool` | Enables built-in position smoothing (lerp toward target) |\n| `position_smoothing_speed` | `float` | Speed of built-in smoothing (default `5.0`) |\n| `drag_horizontal_enabled` | `bool` | Enables a horizontal drag zone; camera only moves when target exits zone |\n| `drag_vertical_enabled` | `bool` | Enables a vertical drag zone |\n| `limit_left` | `int` | Left pixel boundary — camera will not scroll past this |\n| `limit_right` | `int` | Right pixel boundary |\n| `limit_top` | `int` | Top pixel boundary |\n| `limit_bottom` | `int` | Bottom pixel boundary |\n| `zoom` | `Vector2` | Zoom level; `Vector2(2, 2)` = 2× zoom in, `Vector2(0.5, 0.5)` = zoom out |\n\nSet limits to match your tilemap or level bounds so the camera never shows outside the world. Limits are in world pixels, not tiles.\n\n---\n\n## 2. Smooth Follow\n\nA manual follow camera gives more control than the built-in smoothing — you can add look-ahead, offset, and custom easing.\n\n### GDScript\n\n```gdscript\nextends Camera2D\n\n## Target node to follow (assign in Inspector or via code)\n@export var target: Node2D\n\n## How quickly the camera catches up to the target (higher = snappier)\n@export var follow_speed: float = 8.0\n\n## How far ahead the camera leads in the movement direction\n@export var look_ahead_distance: float = 80.0\n\n## How quickly the look-ahead offset responds to direction changes\n@export var look_ahead_speed: float = 4.0\n\nvar _look_ahead_offset: Vector2 = Vector2.ZERO\nvar _previous_target_pos: Vector2 = Vector2.ZERO\n\nfunc _ready() -> void:\n    # Disable built-in smoothing — we handle it manually\n    position_smoothing_enabled = false\n    if target:\n        _previous_target_pos = target.global_position\n        global_position = target.global_position\n\nfunc _process(delta: float) -> void:\n    if not target:\n        return\n\n    # Compute movement direction from last frame\n    var move_delta: Vector2 = target.global_position - _previous_target_pos\n    _previous_target_pos = target.global_position\n\n    # Smoothly steer look-ahead offset toward movement direction\n    var desired_ahead: Vector2 = move_delta.normalized() * look_ahead_distance if move_delta.length() > 0.5 else Vector2.ZERO\n    _look_ahead_offset = _look_ahead_offset.lerp(desired_ahead, look_ahead_speed * delta)\n\n    # Lerp camera position toward target + look-ahead\n    var desired_pos: Vector2 = target.global_position + _look_ahead_offset\n    global_position = global_position.lerp(desired_pos, follow_speed * delta)\n```\n\n### C#\n\n```csharp\nusing Godot;\n\npublic partial class SmoothFollowCamera : Camera2D\n{\n    [Export] public Node2D Target { get; set; }\n    [Export] public float FollowSpeed { get; set; } = 8.0f;\n    [Export] public float LookAheadDistance { get; set; } = 80.0f;\n    [Export] public float LookAheadSpeed { get; set; } = 4.0f;\n\n    private Vector2 _lookAheadOffset = Vector2.Zero;\n    private Vector2 _previousTargetPos = Vector2.Zero;\n\n    public override void _Ready()\n    {\n        PositionSmoothingEnabled = false;\n        if (Target != null)\n        {\n            _previousTargetPos = Target.GlobalPosition;\n            GlobalPosition = Target.GlobalPosition;\n        }\n    }\n\n    public override void _Process(double delta)\n    {\n        if (Target == null)\n            return;\n\n        float dt = (float)delta;\n\n        Vector2 moveDelta = Target.GlobalPosition - _previousTargetPos;\n        _previousTargetPos = Target.GlobalPosition;\n\n        Vector2 desiredAhead = moveDelta.Length() > 0.5f\n            ? moveDelta.Normalized() * LookAheadDistance\n            : Vector2.Zero;\n\n        _lookAheadOffset = _lookAheadOffset.Lerp(desiredAhead, LookAheadSpeed * dt);\n\n        Vector2 desiredPos = Target.GlobalPosition + _lookAheadOffset;\n        GlobalPosition = GlobalPosition.Lerp(desiredPos, FollowSpeed * dt);\n    }\n}\n```\n\n---\n\n## 3. Screen Shake\n\nA trauma-based system produces more natural-looking shake than a simple sine wave. High trauma = violent shake; trauma decays over time; offset scales with `trauma^2` so small trauma values feel subtle.\n\n### GDScript\n\n```gdscript\nclass_name ScreenShakeCamera\nextends Camera2D\n\n## Maximum pixel offset during maximum trauma\n@export var max_offset: Vector2 = Vector2(20.0, 15.0)\n\n## Maximum rotation offset in degrees during maximum trauma\n@export var max_roll: float = 3.0\n\n## Rate at which trauma decays per second (0–1 range)\n@export var decay_rate: float = 1.5\n\nvar _trauma: float = 0.0  # 0.0 = no shake, 1.0 = maximum shake\n\n# Optional: use noise for smooth, organic shake\nvar _noise: FastNoiseLite\nvar _noise_time: float = 0.0\n@export var use_noise: bool = true\n@export var noise_speed: float = 60.0\n\nfunc _ready() -> void:\n    ignore_rotation = false  # default is true (rotation ignored); the roll needs false, which also makes the view follow a rotating parent\n    _noise = FastNoiseLite.new()\n    _noise.noise_type = FastNoiseLite.TYPE_SIMPLEX\n    _noise.seed = randi()\n\n## Call this from any node to trigger a shake (amount in 0–1 range; can stack)\nfunc add_trauma(amount: float) -> void:\n    _trauma = minf(_trauma + amount, 1.0)\n\nfunc _process(delta: float) -> void:\n    if _trauma \u003C= 0.0:\n        offset = Vector2.ZERO\n        rotation = 0.0\n        return\n\n    # Decay trauma over time\n    _trauma = maxf(_trauma - decay_rate * delta, 0.0)\n    _noise_time += delta * noise_speed\n\n    var shake: float = _trauma * _trauma  # squaring gives subtle feel at low trauma\n\n    if use_noise:\n        offset.x = max_offset.x * shake * _noise.get_noise_2d(_noise_time, 0.0)\n        offset.y = max_offset.y * shake * _noise.get_noise_2d(0.0, _noise_time)\n        rotation = deg_to_rad(max_roll) * shake * _noise.get_noise_2d(_noise_time, _noise_time)\n    else:\n        offset.x = max_offset.x * shake * randf_range(-1.0, 1.0)\n        offset.y = max_offset.y * shake * randf_range(-1.0, 1.0)\n        rotation = deg_to_rad(max_roll) * shake * randf_range(-1.0, 1.0)\n```\n\n**Triggering shake from another node:**\n\n```gdscript\n# Any node that can reach the camera\nfunc on_explosion() -> void:\n    var cam := get_viewport().get_camera_2d() as ScreenShakeCamera\n    if cam:\n        cam.add_trauma(0.6)\n```\n\n### C#\n\n```csharp\nusing Godot;\n\npublic partial class ScreenShakeCamera : Camera2D\n{\n    [Export] public Vector2 MaxOffset { get; set; } = new Vector2(20f, 15f);\n    [Export] public float MaxRoll { get; set; } = 3.0f;\n    [Export] public float DecayRate { get; set; } = 1.5f;\n    [Export] public bool UseNoise { get; set; } = true;\n    [Export] public float NoiseSpeed { get; set; } = 60.0f;\n\n    private float _trauma = 0f;\n    private float _noiseTime = 0f;\n    private FastNoiseLite _noise;\n\n    public override void _Ready()\n    {\n        IgnoreRotation = false; \u002F\u002F default is true (rotation ignored); the roll needs false, which also makes the view follow a rotating parent\n        _noise = new FastNoiseLite();\n        _noise.NoiseType = FastNoiseLite.NoiseTypeEnum.Simplex;\n        _noise.Seed = (int)GD.Randi();\n    }\n\n    public void AddTrauma(float amount)\n    {\n        _trauma = Mathf.Min(_trauma + amount, 1.0f);\n    }\n\n    public override void _Process(double delta)\n    {\n        if (_trauma \u003C= 0f)\n        {\n            Offset = Vector2.Zero;\n            Rotation = 0f;\n            return;\n        }\n\n        float dt = (float)delta;\n        _trauma = Mathf.Max(_trauma - DecayRate * dt, 0f);\n        _noiseTime += dt * NoiseSpeed;\n\n        float shake = _trauma * _trauma;\n\n        if (UseNoise)\n        {\n            Offset = new Vector2(\n                MaxOffset.X * shake * _noise.GetNoise2D(_noiseTime, 0f),\n                MaxOffset.Y * shake * _noise.GetNoise2D(0f, _noiseTime)\n            );\n            Rotation = Mathf.DegToRad(MaxRoll) * shake * _noise.GetNoise2D(_noiseTime, _noiseTime);\n        }\n        else\n        {\n            Offset = new Vector2(\n                MaxOffset.X * shake * (float)GD.RandRange(-1.0, 1.0),\n                MaxOffset.Y * shake * (float)GD.RandRange(-1.0, 1.0)\n            );\n            Rotation = Mathf.DegToRad(MaxRoll) * shake * (float)GD.RandRange(-1.0, 1.0);\n        }\n    }\n}\n```\n\n---\n\n## 4. Camera Zones \u002F Rooms\n\nFor room-based games (metroidvanias, top-down dungeons): an `Area2D` per room with a script that, on `body_entered`, tweens the active `Camera2D`'s `limit_left` \u002F `limit_right` \u002F `limit_top` \u002F `limit_bottom` to the room's bounds. Smooth transitions when the player crosses room boundaries.\n\n> See [references\u002Fcamera-zones.md](references\u002Fcamera-zones.md) for the full GDScript + C# CameraZone implementation.\n\n---\n\n## 5. Camera3D Patterns\n\nThree canonical 3D camera setups: **third-person follow** with `SpringArm3D` (handles wall collision), **orbit camera** with mouse-drag rotation, **first-person** with mouse-look-from-camera.\n\n> See [references\u002Fcamera3d-patterns.md](references\u002Fcamera3d-patterns.md) for full GDScript implementations of each pattern.\n\n---\n\n## 6. Camera Transitions\n\nAsync camera transitions via `Tween` + `await` (`ToSignal` in C#). The pattern: tween the current camera's position and zoom (2D) or FOV (3D) to the next camera's values, then call `make_current()` on the next camera. Works for both 2D and 3D.\n\n> See [references\u002Ftransitions.md](references\u002Ftransitions.md) for the full `CameraTransitionManager` implementation (2D and 3D).\n\n---\n\n## 7. Split Screen (Local Multiplayer)\n\nRender multiple cameras to separate `SubViewport`s, then arrange `SubViewportContainer`s in a layout (`HBoxContainer`, `VBoxContainer`, or `GridContainer`). Each player's camera is set as `current` for its viewport.\n\n> See [references\u002Fsplit-screen.md](references\u002Fsplit-screen.md) for the SubViewport scene-tree setup and a 2-player split-screen example.\n\n---\n\n## 8. Implementation Checklist\n\n- [ ] `Camera2D` limits match level\u002Ftilemap bounds so no out-of-world edges are visible\n- [ ] Smooth follow uses `_process` (visual interpolation), not `_physics_process`\n- [ ] Screen shake resets `offset` and `rotation` to zero when `_trauma` reaches `0.0`\n- [ ] `add_trauma()` clamps to `1.0`; it does not exceed maximum shake\n- [ ] Camera zone `Area2D` collision layers are set so only the player triggers them\n- [ ] `SpringArm3D` collision mask includes all environment layers for wall avoidance\n- [ ] Camera transition awaits tween completion before calling `make_current()`\n- [ ] Split screen `SubViewport` sizes are updated on window resize (`get_tree().root.size_changed` signal)\n- [ ] Only one `SubViewport` has `audio_listener_enable_2d` or `audio_listener_enable_3d` set to `true`\n",{"name":5,"description":7},"master","60c28fdc40706a24576731e9fb3508b9c95258ad","skills\u002Fcamera-system",63,"https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter","https:\u002F\u002Fgithub.com\u002Fjame581","https:\u002F\u002Fjanmesarc.online\u002F","Software engineer. My biggest passion is game development.","2026-11-13 02:28:46",{"source":8,"skillName":5,"commands":45,"prompt":55,"zipAvailable":56},[46,49,52],{"tool":47,"command":48},"npx","npx skills add jame581\u002FGodotPrompter --skill camera-system",{"tool":50,"command":51},"bunx","bunx skills add jame581\u002FGodotPrompter --skill camera-system",{"tool":53,"command":54},"pnpm","pnpm dlx skills add jame581\u002FGodotPrompter --skill camera-system","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Fcamera-system\u002FSKILL.md",true,[58,64,68,72],{"path":59,"resourceType":60,"resourceTypeDesc":61,"sizeBytes":62,"sourceHtmlUrl":63},"references\u002Fcamera-zones.md","REFERENCE","参考文档",3506,"https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Fcamera-system\u002Freferences\u002Fcamera-zones.md",{"path":65,"resourceType":60,"resourceTypeDesc":61,"sizeBytes":66,"sourceHtmlUrl":67},"references\u002Fcamera3d-patterns.md",6471,"https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Fcamera-system\u002Freferences\u002Fcamera3d-patterns.md",{"path":69,"resourceType":60,"resourceTypeDesc":61,"sizeBytes":70,"sourceHtmlUrl":71},"references\u002Fsplit-screen.md",4300,"https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Fcamera-system\u002Freferences\u002Fsplit-screen.md",{"path":73,"resourceType":60,"resourceTypeDesc":61,"sizeBytes":74,"sourceHtmlUrl":75},"references\u002Ftransitions.md",4916,"https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Fcamera-system\u002Freferences\u002Ftransitions.md",[77,97,118,148,172,189],{"skillNo":78,"name":79,"nameZh":6,"description":80,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":81,"categories":82,"keywords":83,"publishedAt":30},"01M6KRFE5BR2PSD08ZKDAS66R7","xr-development","Use when building VR\u002FAR\u002FXR applications — OpenXR setup, XROrigin3D, hand tracking, controllers, passthrough, and Meta Quest deployment in Godot 4.3+","https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Fxr-development\u002FSKILL.md",[],[84,85,29,86,87,88,89,90,91,92,93,26,94,95,96],"xr development","development","deployment","godot","vr","setup","tracking","godot 4","ar","hand","Meta","applications","quest",{"skillNo":98,"name":99,"nameZh":6,"description":100,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":101,"categories":102,"keywords":103,"publishedAt":30},"01M6KRF8GZTG1TAS506MFBSGFC","web-services","Use when a Godot game calls a web server — HTTPRequest, downloads, REST\u002FJSON APIs, auth headers, WebSocketPeer clients, TLS, retries, and keeping secrets out of the build. Not for game networking\u002FRPCs (multiplayer-basics) or browser export (web-export)","https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Fweb-services\u002FSKILL.md",[],[104,105,106,107,108,109,110,111,26,29,87,112,113,114,115,116,117],"web services","web","services","tls","retries","calling","auth","downloads","clients","headers","secrets","json","build","REST",{"skillNo":119,"name":120,"nameZh":6,"description":121,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":122,"categories":123,"keywords":129,"publishedAt":30},"01M6KRF38Q4WSKAYFS56PG87T0","web-export","Use when shipping a Godot game to the browser — Web export preset options, threads vs no-threads and COOP\u002FCOEP headers, hosting on itch.io or your own server, PWA, JavaScriptBridge, user:\u002F\u002F persistence, audio and input limits, and load size","https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Fweb-export\u002FSKILL.md",[124],{"categoryNo":125,"categoryName":126,"taxonomyType":127,"taxonomyTypeDesc":128},"01JBSEEDCAT000000000000016","图像与多媒体","SCENARIO","使用场景",[105,130,131,132,133,134,135,136,113,137,138,29,139,140,141,142,143,144,26,145,146,147],"export","web export","threads","limits","shipping","size","options","hosting","input","Coop","preset","browser","game","io","load","pwa","Audio","server",{"skillNo":149,"name":150,"nameZh":6,"description":151,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":152,"categories":153,"keywords":162,"publishedAt":30},"01M6KREYFETEBH62ZTN6EZH2X3","using-godot-prompter","Bootstrap skill — establishes how to find and use GodotPrompter skills, with platform-specific tool mapping","https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Fusing-godot-prompter\u002FSKILL.md",[154,157],{"categoryNo":155,"categoryName":156,"taxonomyType":127,"taxonomyTypeDesc":128},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":158,"categoryName":159,"taxonomyType":160,"taxonomyTypeDesc":161},"01JBSEEDCAT000000000000032","设计师","PROFESSION","适用职业",[87,163,164,165,166,26,167,168,169,170,171],"using godot prompter","find","with","bootstrap","tool","platform","mapping","how","how to",{"skillNo":173,"name":174,"nameZh":6,"description":175,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":176,"categories":177,"keywords":178,"publishedAt":30},"01M6KRER5G7EQWKGAFN8QJKQ69","tween-animation","Use when implementing tweens — property animation, method tweening, chaining, parallel sequences, easing, and common UI\u002Fgameplay motion recipes","https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Ftween-animation\u002FSKILL.md",[],[179,180,26,181,182,183,184,185,186,187,24,29,188],"tween animation","Animation","motion","parallel","gameplay","chaining","ui","recipes","common","sequences",{"skillNo":190,"name":191,"nameZh":6,"description":192,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":193,"categories":194,"keywords":195,"publishedAt":30},"01M6KREKGCQMVDV8WX7HWX8BZG","state-machine","Use when building a state machine or refactoring logic into one — enum, node, and resource-based FSM patterns with trade-offs. Not for adding states to an existing one","https:\u002F\u002Fgithub.com\u002Fjame581\u002FGodotPrompter\u002Fblob\u002Fmaster\u002Fskills\u002Fstate-machine\u002FSKILL.md",[],[196,197,198,87,29,199,200,201,202,203,165,26,24,204],"state","state machine","machine","patterns","node","fsm","state machines","trade","trade-offs"]