About godot-2d-gameplay
godot-2d-gameplay is a skill for implementing 2D gameplay within a Godot project's existing conventions. It serves developers building movement, physics, combat, and feedback, with workflows for inspecting project evidence and verifying edge cases rather than applying snippets unchanged.
Key features
- 01Inspect Godot version and project configuration
- 02Preserve existing scene and input conventions
- 03Choose physics nodes for gameplay roles
- 04Build movement and combat interactions
- 05Verify collision, damage, and performance edge cases
- 06Troubleshoot common 2D gameplay failures
Use cases
- 01Implementing a platformer or top-down controller
- 02Adding collision-based attacks and damage
- 03Debugging TileMap collision alignment
- 04Checking gameplay behavior across physics conditions
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ pbzona/ godot-skills --skill godot-2d-gameplayAfter installing, type/godot-2d-gameplayin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/animation-combat-game-feel.mdmovement-physics-tilemaps.mdverification-and-troubleshooting.mdDetails
From the project READMEGodot 2D Gameplay
Implement gameplay inside the project's existing scene, input, physics, and component conventions. Treat snippets as patterns to adapt, not files to paste unchanged.
Start With Project Evidence
Before proposing nodes or code:
- Locate the nearest
project.godot. Readconfig/featuresand, when a Godot executable is available, confirm it withgodot --versionor the project's documented binary. - Inspect
application/run/main_scene,[input], named 2D physics layers, physics tick rate, default gravity, window/stretch settings, and texture filtering. - Search existing
.gd,.tscn, and.tresfiles for player bodies,TileMaporTileMapLayer, animation nodes, damage APIs, groups, autoloads, and test tooling. - Preserve node names, typed-script style, signal naming, scene ownership, and collision conventions already in use.
- Do not migrate a working
TileMapmerely to match an example. For new work, preferTileMapLayeronly when the inspected Godot version supports it.
If no project file exists, ask for the target Godot 4.x minor version and a minimal scene contract before generating project-specific files.
Choose the Physics Owner
- Use
CharacterBody2Dfor code-driven actors that must slide against world collision. Use grounded motion for platformers and floating motion for top-down actors. - Use
RigidBody2Dfor simulation-driven objects. Apply impulses for discrete hits and forces for sustained acceleration; do not overwrite its transform each frame. - Use
AnimatableBody2Dfor scripted moving platforms and hazards that should transfer motion predictably. - Use
Area2Dfor overlap-only sensors, pickups, hurtboxes, and most simple projectiles. It does not provide swept body collision. - Keep physics movement and queries in
_physics_process(). Keep presentation updates in_process()unless they must match the physics result exactly.
Implementation Workflow
- Define behavior in measurable terms. Record speed, acceleration, stopping distance, jump height or airtime, attack cadence, invulnerability, and expected collision partners.
- Create inputs first. Reuse semantic Input Map actions. Never hardcode keys in actor logic. Verify keyboard and controller dead zones where both are supported.
- Build a gray-box physics scene. Use primitive collision shapes and a plain floor before adding art, animation, cameras, or combat.
- Implement one movement mode. Keep velocity in pixels per second;
CharacterBody2D.move_and_slide()applies the physics timestep internally. - Build the world in layers. Separate ground, collision-bearing terrain, decoration, foreground, and navigation according to actual responsibilities.
- Derive visuals from gameplay state. Select animation after movement and avoid letting animation tracks overwrite authoritative physics transforms.
- Add combat as explicit interactions. Give attacks, hurtboxes, health, teams, cooldowns, and death separate contracts. Use signals for feedback.
- Add game feel last. Layer readable effects around confirmed events; never use shake or animation to hide unreliable collision.
- Verify at edges and under load. Test slopes, corners, low ceilings, simultaneous hits, scene reloads, pooled reuse, and the highest expected actor/projectile count.
Typed Platformer Core
This controller uses rate-based acceleration, buffered jumping, coyote time, and a short-hop release. Tune values against the project's scale.
extends CharacterBody2D
@export_range(0.0, 2000.0, 1.0) var run_speed: float = 260.0
@export_range(0.0, 10000.0, 1.0) var ground_accel: float = 2200.0
@export_range(0.0, 10000.0, 1.0) var air_accel: float = 900.0
@export_range(0.0, 10000.0, 1.0) var gravity: float = 1800.0
@export_range(0.0, 5000.0, 1.0) var jump_speed: float = 560.0
@export_range(0.0, 5000.0, 1.0) var max_fall_speed: float = 1100.0
@export_range(0.0, 0.5, 0.01) var coyote_time: float = 0.10
@export_range(0.0, 0.5, 0.01) var jump_buffer_time: float = 0.12
var _coyote_left: float = 0.0
var _jump_buffer_left: float = 0.0
func _physics_process(delta: float) -> void:
if Input.is_action_just_pressed(&"jump"):
_jump_buffer_left = jump_buffer_time
else:
_jump_buffer_left = maxf(_jump_buffer_left - delta, 0.0)
var axis: float = Input.get_axis(&"move_left", &"move_right")
var accel: float = ground_accel if is_on_floor() else air_accel
velocity.x = move_toward(velocity.x, axis * run_speed, accel * delta)
if is_on_floor():
_coyote_left = coyote_time
else:
_coyote_left = maxf(_coyote_left - delta, 0.0)
velocity.y = minf(velocity.y + gravity * delta, max_fall_speed)
if _jump_buffer_left > 0.0 and _coyote_left > 0.0:
velocity.y = -jump_speed
_jump_buffer_left = 0.0
_coyote_left = 0.0
if Input.is_action_just_released(&"jump") and velocity.y < 0.0:
velocity.y *= 0.45
move_and_slide()
Do not multiply velocity by delta before move_and_slide(). If jump height
must be exact, derive gravity and launch speed from the desired height and time
to apex instead of tuning both independently.
Design Rules
- Size collision for playability, not sprite transparency. Keep feet stable and combat hurtboxes intentionally forgiving.
- Treat collision layers as an interface. Name each layer and document which masks consume it.
- Change animation only when the semantic state changes. Give airborne and action states priority over idle/run state.
- Apply damage once at the authoritative receiver. Feedback listeners may flash, shake, spawn particles, update UI, or play audio, but must not apply a second hit.
- Use timers or accumulated game delta for cooldowns. Wall-clock timestamps do not naturally honor pause or time scale.
- Pool only when profiling or scale justifies it. A pooled node needs explicit spawn/despawn reset for timers, monitoring, velocity, ownership, and process state; visibility alone is not a lifecycle.
Verification Gate
Before finishing:
- Run the repository's existing test command and a headless parse/import check if a compatible Godot binary is available.
- Confirm every referenced Input Map action exists.
- Enable visible collision shapes and inspect tile collision, body shape, and Area2D masks in the running scene.
- Verify movement at the configured physics tick rate and after a window or camera scale change.
- Verify damage clamps, death emits once, friendly targets are ignored, and invulnerability cannot become permanent.
- For pools, watch the Remote scene tree and profiler: count should stabilize, and a reused object must behave like a fresh instance.
- Report files changed, scenes exercised, commands run, and any checks blocked by missing assets or a missing Godot executable.
Common Failure Modes
- Diagonal top-down movement is faster: use
Input.get_vector()or clamp vector length. - Character speed changes with frame rate: keep movement in physics process
and use
deltaonly for rates, not the final CharacterBody velocity. - Floor state is always false: check
up_direction, motion mode, masks, collision shapes, and call order aroundmove_and_slide(). - Tile collision looks offset: compare TileSet tile size, atlas region size, layer transform, and collision polygons.
- Animation flickers: multiple branches call
play()or state priority is ambiguous. Resolve one state, then apply it once. - An attack hits repeatedly: disable or consume the hitbox, track victims per activation, or add receiver invulnerability.
- Fast bullets tunnel: use a swept body/ray query, shorter physics travel, or a shape cast rather than relying on overlap at the final position.
- Shake leaves the camera displaced: decay toward zero and explicitly reset offset when the effect ends.