About godot-3d-gameplay
A skill for implementing 3D gameplay in Godot while preserving a project's architecture. It covers physics, cameras, lighting, and rendering, with inspection and in-game verification guiding design choices.
Key features
- 01Inspect project settings and existing scenes
- 02Choose physics bodies for gameplay roles
- 03Build typed player controllers with physics ticks
- 04Configure camera rigs and input actions
- 05Tune environment lighting for readability
- 06Test gameplay, visuals, and performance
Use cases
- 01Adding a player controller to an existing Godot project
- 02Integrating camera collision with level geometry
- 03Tuning lighting for a target renderer
- 04Verifying movement across slopes and platforms
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ pbzona/ godot-skills --skill godot-3d-gameplayAfter installing, type/godot-3d-gameplayin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/controllers-and-cameras.mdenvironments-and-lighting.mdphysics-testing-and-debugging.mdDetails
From the project READMEGodot 3D Gameplay
Implement 3D gameplay as a coordinated physics, camera, and rendering system. Preserve the project's existing architecture and tune from observed behavior rather than replacing working systems with a generic controller.
Inspect Before Editing
Do not write scenes, scripts, or project settings until this inspection is complete.
- Find
project.godotand confirm the project root. Readconfig/features, the rendering method, physics tick rate, display settings, 3D physics layer names, and existing Input Map actions. - Run the project's established Godot executable with
--versionwhen available. Match APIs to that exact Godot 4.x release; do not silently upgrade the project or its resource formats. - Inspect representative
.tscn,.gd, and imported 3D assets. Record scene ownership, node naming, typed GDScript style, signal style, groups, autoloads, folder layout, and whether node references use exported nodes, unique names, or paths. - Inspect the current player, camera, level, environment, collision layers and masks, model scale, applied transforms, and input bindings. Check whether meshes or resources are shared before editing them.
- Identify the target renderer and hardware. A Forward+ desktop scene, a Mobile-renderer game, and a Compatibility project require different lighting and environment choices.
- Establish a runnable baseline and note existing warnings. Do not attribute pre-existing errors to the requested change.
If the editor version, project feature tag, and requested API disagree, stop and resolve the version mismatch before authoring resources.
Choose the Smallest Suitable Design
- Use
CharacterBody3Dfor script-directed avatars that need floor, wall, slope, and moving-platform behavior. - Use
AnimatableBody3Dfor script- or animation-driven platforms that should carry characters. - Use
RigidBody3Dfor bodies whose motion should come from forces and impulses. Do not drive one like a character by overwriting its transform every frame. - Keep collision, gameplay state, visuals, and camera pivots in separate child branches when they change for different reasons.
- Use Input Map actions, not physical key constants, so keyboard and gamepad bindings can share gameplay code.
- Use a
SpringArm3Dcamera rig when level geometry can move between the subject and camera. - Start with one
WorldEnvironmentand one primary light. Add post-processing and extra shadowed lights only after the base image reads clearly.
Implementation Workflow
- Define observable behavior: movement reference frame, top speed, acceleration, air control, slope limit, jump rules, camera range, camera collision, and visual target.
- Build or adapt a reusable player scene. Align the capsule or other simple collider to the feet, keep the visual model transform local, and leave the root at identity for clean instancing.
- Confirm action names and deadzones in the Input Map. Reuse project actions where their semantics match.
- Implement physics in
_physics_process(delta). TreatCharacterBody3D.velocityas units per second and callmove_and_slide()once per physics tick. - Implement look events in
_unhandled_input(event)unless the project has an input router. Keep yaw and pitch on separate pivots and exclude the player collider from the spring arm. - Establish the environment and lighting for readability first. Tune sky or background, ambient contribution, key-light direction, exposure, fog, and shadows as one composition.
- Test controller, camera, and lighting together in representative geometry before polishing animation or effects.
Typed Controller Example
Adapt names and structure to the inspected project. Assign view_camera in the Inspector and define move_left, move_right, move_forward, move_back, and jump actions.
extends CharacterBody3D
@export_range(0.1, 30.0, 0.1) var move_speed: float = 7.0
@export_range(0.1, 100.0, 0.1) var ground_acceleration: float = 28.0
@export_range(0.1, 100.0, 0.1) var air_acceleration: float = 9.0
@export_range(0.1, 30.0, 0.1) var jump_velocity: float = 8.5
@export var view_camera: Camera3D
var gravity: float = float(
ProjectSettings.get_setting("physics/3d/default_gravity", 9.8)
)
func _ready() -> void:
assert(is_instance_valid(view_camera), "Assign the movement camera")
func _physics_process(delta: float) -> void:
var move_input: Vector2 = Input.get_vector(
&"move_left", &"move_right", &"move_forward", &"move_back"
)
# Camera +Z points backward, matching the positive Y returned for move_back.
var camera_back: Vector3 = view_camera.global_transform.basis.z
var camera_right: Vector3 = view_camera.global_transform.basis.x
camera_back.y = 0.0
camera_right.y = 0.0
camera_back = camera_back.normalized()
camera_right = camera_right.normalized()
var wish_direction: Vector3 = (
camera_right * move_input.x + camera_back * move_input.y
).limit_length(1.0)
var target_velocity: Vector3 = wish_direction * move_speed
var response: float = (
ground_acceleration if is_on_floor() else air_acceleration
)
velocity.x = move_toward(velocity.x, target_velocity.x, response * delta)
velocity.z = move_toward(velocity.z, target_velocity.z, response * delta)
if is_on_floor():
if velocity.y < 0.0:
velocity.y = 0.0
if Input.is_action_just_pressed(&"jump"):
velocity.y = jump_velocity
else:
velocity.y -= gravity * delta
move_and_slide()
Tune floor_max_angle, floor_snap_length, floor_stop_on_slope, platform layers, and platform_on_leave on the body rather than recreating those behaviors ad hoc. Add coyote time, jump buffering, sprinting, or root motion only when the game specification calls for them.
Verification
Run the project's own checks first, then perform all applicable steps:
- Import and parse with the matching editor. If the project supports headless validation, use
<godot> --headless --path <project> --editor --quitand inspect the full output. - Run the player scene and at least one integrated level. Confirm there is exactly one active gameplay camera.
- Test start, stop, reverse, diagonal input, no-input deceleration, jump press versus hold, ceiling contact, ledges, stairs, shallow and over-limit slopes, walls, corners, and moving platforms.
- Rotate the camera through its full range. Check movement orientation, pitch clamps, camera collision, recovery after an obstruction, near-clip intersections, and mouse capture release.
- Test at low and high rendered frame rates while keeping physics behavior stable. Look for frame-dependent camera smoothing and animation code.
- Use visible collision shapes and the remote scene tree to verify collider alignment, layers, masks, active environment, and runtime transforms.
- Compare representative dark, bright, indoor, and outdoor views. Check exposure, silhouettes, shadow acne, peter-panning, fog readability, and material response.
- Profile on target-class hardware before retaining expensive shadows, volumetric effects, screen-space effects, or many dynamic lights.
- Re-run the baseline and report changed files, settings, tests, and any device or visual checks that could not be performed.
Pitfalls
- Multiplying
CharacterBody3D.velocitybydeltabeforemove_and_slide(); the body expects velocity, not per-frame displacement. - Moving a physics body by
positionorglobal_transformand then expecting slide collisions or floor state to be reliable. - Using the camera's pitched forward vector directly, which adds unwanted vertical movement.
- Parenting the camera directly to animated visual bones or rotating the entire player to aim the camera.
- Giving detailed render meshes equally detailed collision, or using non-uniformly scaled collision shapes.
- Reading
is_on_floor()as a predictive query; it describes the previousmove_and_slide()result. - Hard-coding keys, collision layer numbers, node paths, or gravity when project settings already define them.
- Enabling every environment effect before checking renderer support and GPU cost.
- Editing a shared
Environment, material, mesh, or shape resource when only one scene instance should change. - Treating a headless parse as visual verification; cameras, materials, shadows, and fog still require rendered inspection.