
Godot Genre Platformer
- 198 installs
- 454 repo stars
- Updated July 28, 2026
- thedivergentai/gd-agentic-skills
Use godot-genre-platformer for development tasks
About
godot-genre-platformer: A skill for development. This provides functionality for development workflows.
- godot-genre-platformer
Godot Genre Platformer by the numbers
- 198 all-time installs (skills.sh)
- +17 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #2,050 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-genre-platformerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 198 |
|---|---|
| repo stars | ★ 454 |
| Last updated | July 28, 2026 |
| Repository | thedivergentai/gd-agentic-skills ↗ |
What it does
Use godot-genre-platformer for development tasks
Files
Genre: Platformer
Expert blueprint for platformers emphasizing movement feel, level design, and player satisfaction.
NEVER Do (Expert Anti-Patterns)
Physics & Movement Feel
- NEVER multiply velocity by
deltabeforemove_and_slide(); the method internalizes the timestep. - NEVER skip Coyote Time (approx 0.1s); without this grace period, jumps will feel unresponsive when walking off ledges.
- NEVER ignore Jump Buffering (approx 0.15s); players expect to jump the instant they touch the ground if they pressed the button early.
- NEVER use a fixed jump height; strictly implement Variable Jump Height (cut velocity on release) for player expression.
- NEVER forget to scale gravity by
deltabefore adding to velocity; gravity is an acceleration and must be frame-rate independent. - NEVER rely on discrete collision for high-speed movement; strictly use
CCD_MODE_CAST_RAYto prevent tunneling through geometry. - NEVER use
move_and_collide()for standard traversal; it lacks the slope/stair handling ofmove_and_slide(). - NEVER check coyote or buffer timers using exact equality (== 0.0); strictly use
is_equal_approx()or>= 0.0.
Polish & Level Design
- NEVER use linear camera snapping; strictly use Camera Smoothing or
lerp()to prevent motion sickness. - NEVER skip Squash and Stretch on jump/land; movement feels weightless without these subtle visual "juice" cues.
- NEVER create Blind Jumps; strictly use camera look-ahead or zoom triggers to reveal landing zones.
- NEVER use individual
Sprite2Dnodes for level geometry; strictly use TileMapLayer for optimized collision and rendering. - NEVER use complex/concave
CollisionShape2Dfor the player; strictly favor primitive shapes (Capsule/Rectangle) for stability.
Architecture & Performance
- NEVER use
CharacterBody2Dfor simple moving platforms; strictly use AnimatableBody2D and enablesync_to_physics. - NEVER ignore
platform_on_leavefor descending platforms; usePLATFORM_ON_LEAVE_ADD_UPWARD_VELOCITYto preserve jump impulse. - NEVER disable
recovery_as_collisionon the player character; it is required for correct floor snapping reports. - NEVER use the
!(NOT) operator in AnimationTree expressions; strictly useis_walking == false. - NEVER use standard Strings for high-frequency state checks; strictly use
StringName(e.g.,&"jumping"). - NEVER load heavy level chunks synchronously; strictly use
ResourceLoader.load_threaded_request()to prevent frame stutters.
---
🛠 Expert Components (scripts/)
Original Expert Patterns
- advanced_platformer_controller.gd - Professional-grade
CharacterBody2Dcontroller with Coyote Time, Jump Buffering, and variable height.
Modular Components
- coyote_timer.gd - Grace period logic for jumps after leaving a floor's edge.
- jump_buffer.gd - Input queuing system for ultra-responsive landing jumps.
- player_ground_controller.gd - Advanced movement with floor constant speed and slope-aware snapping.
- variable_jump.gd - Scalable jump height using velocity cutoff on button release.
- wall_slide_sensor.gd - Nodeless wall detection using high-performance physics raycasts.
- ledge_grab_sensor.gd - PhysicsShapeQuery-based ledge detection without Area2D nodes.
- custom_collision_slider.gd - Manual sliding response for high-speed inter-frame precision.
- synchronized_platform.gd -
AnimatableBody2Dconfig for physics-synced movement. - fast_projectile_ccd.gd - Continuous Collision Detection setup to prevent tunneling.
- platformer_animation_sync.gd - Boolean-safe sync between physics states and AnimationTree.
- platformer_camera.gd - Camera smoothing and look-ahead logic for platforming focus.
---
Core Loop
Jump → Navigate Obstacles → Reach Goal → Next Level
Skill Chain
godot-project-foundations, godot-characterbody-2d, godot-input-handling, animation, sound-manager, tilemap-setup, camera-2d
---
Movement Feel ("Game Feel")
The most critical aspect of platformers. Players should feel precise, responsive, and in control.
Input Responsiveness
# Instant direction changes - no acceleration on ground
func _physics_process(delta: float) -> void:
var input_dir := Input.get_axis("move_left", "move_right")
# Ground movement: instant response
if is_on_floor():
velocity.x = input_dir * MOVE_SPEED
else:
# Air movement: slightly reduced control
velocity.x = move_toward(velocity.x, input_dir * MOVE_SPEED, AIR_ACCEL * delta)Coyote Time (Grace Period)
Allow jumping briefly after leaving a platform:
var coyote_timer: float = 0.0
const COYOTE_TIME := 0.1 # 100ms grace period
func _physics_process(delta: float) -> void:
if is_on_floor():
coyote_timer = COYOTE_TIME
else:
coyote_timer = max(0, coyote_timer - delta)
# Can jump if on floor OR within coyote time
if Input.is_action_just_pressed("jump") and coyote_timer > 0:
velocity.y = JUMP_VELOCITY
coyote_timer = 0Jump Buffering
Register jumps pressed slightly before landing:
var jump_buffer: float = 0.0
const JUMP_BUFFER_TIME := 0.15
func _physics_process(delta: float) -> void:
if Input.is_action_just_pressed("jump"):
jump_buffer = JUMP_BUFFER_TIME
else:
jump_buffer = max(0, jump_buffer - delta)
if is_on_floor() and jump_buffer > 0:
velocity.y = JUMP_VELOCITY
jump_buffer = 0Variable Jump Height
const JUMP_VELOCITY := -400.0
const JUMP_RELEASE_MULTIPLIER := 0.5
func _physics_process(delta: float) -> void:
# Cut jump short when button released
if Input.is_action_just_released("jump") and velocity.y < 0:
velocity.y *= JUMP_RELEASE_MULTIPLIERGravity Tuning
const GRAVITY := 980.0
const FALL_GRAVITY_MULTIPLIER := 1.5 # Faster falls feel better
const MAX_FALL_SPEED := 600.0
func apply_gravity(delta: float) -> void:
var grav := GRAVITY
if velocity.y > 0: # Falling
grav *= FALL_GRAVITY_MULTIPLIER
velocity.y = min(velocity.y + grav * delta, MAX_FALL_SPEED)---
Level Design Principles
The "Teaching Trilogy"
1. Introduction: Safe environment to learn mechanic 2. Challenge: Apply mechanic with moderate risk 3. Twist: Combine with other mechanics or time pressure
Visual Language
- Safe platforms: Distinct color/texture
- Hazards: Red/orange tints, spikes, glow effects
- Collectibles: Bright, animated, particle effects
- Secrets: Subtle environmental hints
Flow and Pacing
Easy → Easy → Medium → CHECKPOINT → Medium → Hard → CHECKPOINT → BossCamera Design
# Look-ahead camera for platformers
extends Camera2D
@export var look_ahead_distance := 100.0
@export var look_ahead_speed := 3.0
var target_offset := Vector2.ZERO
func _process(delta: float) -> void:
var player_velocity: Vector2 = target.velocity
var desired_offset := player_velocity.normalized() * look_ahead_distance
target_offset = target_offset.lerp(desired_offset, look_ahead_speed * delta)
offset = target_offset---
Platformer Sub-Genres
Precision Platformers (Celeste, Super Meat Boy)
- Instant respawn on death
- Very tight controls (no acceleration)
- Checkpoints every few seconds of gameplay
- Death is learning, not punishment
Collectathon (Mario 64, Banjo-Kazooie)
- Large hub worlds with objectives
- Multiple abilities unlocked over time
- Backtracking encouraged
- Stars/collectibles as progression gates
Puzzle Platformers (Limbo, Inside)
- Slow, deliberate pacing
- Environmental puzzles
- Physics-based mechanics
- Atmospheric storytelling
Metroidvania (Hollow Knight)
- See
godot-genre-metroidvaniaskill - Ability-gated exploration
- Interconnected world map
---
Common Pitfalls
| Pitfall | Solution |
|---|---|
| Floaty jumps | Increase gravity, especially on descent |
| Imprecise landings | Add coyote time and visual landing feedback |
| Unfair deaths | Ensure hazards are clearly visible before encountered |
| Blind jumps | Camera look-ahead or zoom out during falls |
| Boring mid-game | Introduce new mechanics every 2-3 levels |
---
Polish Checklist
- [ ] Dust godot-particles on land/run
- [ ] Screen shake on heavy landings
- [ ] Squash/stretch animations
- [ ] Sound effects for every action (jump, land, wall-slide)
- [ ] Death and respawn animations
- [ ] Checkpoint visual/audio feedback
- [ ] Accessible difficulty options (assist mode)
---
Godot-Specific Tips
1. CharacterBody2D vs RigidBody2D: Always use CharacterBody2D for platformer characters - precise control is essential 2. Physics tick rate: Consider 120Hz physics for smoother movement 3. One-way platforms: Use set_collision_mask_value() or dedicated collision layers 4. Wall detection: Use is_on_wall() and get_wall_normal() for wall jumps
---
Example Games for Reference
- Celeste - Perfect game feel, assist mode accessibility
- Hollow Knight - Combat + platforming integration
- Super Mario Bros. Wonder - Visual polish and surprises
- Shovel Knight - Retro mechanics with modern feel
Advanced Platformer Mechanics
Elite implementation of competitive features, procedural world-building, and specialized physics.
1. Squash and Stretch Helper (Visual Juice)
To apply high-fidelity visual juice, modify the scale of the character's visual node using Tween. This provides non-linear interpolation for bouncy, responsive movement that makes actions like jumping and landing feel physically grounded.
class_name GameFeelHelper extends Node
func apply_squash_and_stretch(visual_node: Node2D, target_scale: Vector2, duration: float) -> void:
var tween := visual_node.create_tween()
tween.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
# Squash
tween.tween_property(visual_node, "scale", target_scale, duration)
# Return to normal
tween.tween_property(visual_node, "scale", Vector2.ONE, duration)2. Particle-Trails
Enable 3D or 2D particle trails by configuring GPUParticles3D with the trail_enabled property. This creates a procedural trail of meshes or sprites that follows the player, enhancing the sense of speed and direction.
class_name SpeedTrail extends GPUParticles3D
func toggle_trail(active: bool) -> void:
emitting = active
trail_enabled = true
trail_lifetime = 0.5
# Configure via shader or process_material for color fading3. Checkpoint-System
Implement a reliable checkpoint system by recording the player's global_position upon entering a designated Area2D trigger. Store the checkpoint as a Resource to ensure it persists across scene transitions and game restarts.
class_name Checkpoint extends Area2D
@export var checkpoint_id: StringName
func _on_body_entered(body: Node2D) -> void:
if body.is_in_group("player"):
SaveManager.current_checkpoint_pos = global_position
SaveManager.last_checkpoint_id = checkpoint_id
# Play visual/audio feedback
play_activation_effects()Expert Tip: For the "Squash and Stretch" effect, ensure the visual node's pivot point is at the character's feet (bottom center) so the scaling happens from the ground up.
Reference
- Master Skill: godot-master
Reference
- Master Skill: godot-master
# skills/genre-platformer/scripts/advanced_platformer_controller.gd
extends CharacterBody2D
## Advanced Platformer Controller
## Expert implementation of "game feel" mechanics: Coyote Time, Jump Buffering, Apex Modifiers.
class_name AdvancedPlatformerController
@export_group("Movement")
@export var move_speed: float = 200.0
@export var acceleration: float = 1800.0
@export var friction: float = 2000.0
@export var air_acceleration: float = 1200.0
@export var air_friction: float = 800.0
@export_group("Jump")
@export var jump_height: float = 80.0
@export var jump_time_to_peak: float = 0.35
@export var jump_time_to_descent: float = 0.3
@export var variable_jump_height: bool = true
@export_group("Assists")
@export var coyote_time: float = 0.1
@export var jump_buffer: float = 0.15
@export var apex_duration: float = 0.1
@export var apex_gravity_mult: float = 0.5
# Detailed Physics Calculation
@onready var jump_velocity: float = ((2.0 * jump_height) / jump_time_to_peak) * -1.0
@onready var jump_gravity: float = ((-2.0 * jump_height) / (jump_time_to_peak * jump_time_to_peak)) * -1.0
@onready var fall_gravity: float = ((-2.0 * jump_height) / (jump_time_to_descent * jump_time_to_descent)) * -1.0
# State
var _coyote_timer: float = 0.0
var _jump_buffer_timer: float = 0.0
var _is_jumping: bool = false
func _physics_process(delta: float) -> void:
# 1. Input Handling
var input_x = Input.get_axis("move_left", "move_right")
# 2. Gravity Application
var gravity_mult = 1.0
# Apex modifier: reduce gravity at the very peak of jump for "hang time"
if _is_jumping and abs(velocity.y) < 100.0:
gravity_mult = apex_gravity_mult
if velocity.y < 0:
velocity.y += jump_gravity * gravity_mult * delta
else:
velocity.y += fall_gravity * gravity_mult * delta
# 3. Timers
if is_on_floor():
_coyote_timer = coyote_time
_is_jumping = false
else:
_coyote_timer -= delta
if Input.is_action_just_pressed("jump"):
_jump_buffer_timer = jump_buffer
else:
_jump_buffer_timer -= delta
# 4. Jump Execution
if _jump_buffer_timer > 0 and (_coyote_timer > 0 or is_on_floor()):
velocity.y = jump_velocity
_is_jumping = true
_jump_buffer_timer = 0
_coyote_timer = 0
# Variable Jump Height (releasing button cuts jump short)
if variable_jump_height and Input.is_action_just_released("jump") and velocity.y < 0:
velocity.y *= 0.5
# 5. Horizontal Movement
var target_accel = acceleration if is_on_floor() else air_acceleration
var target_friction = friction if is_on_floor() else air_friction
if input_x != 0:
velocity.x = move_toward(velocity.x, input_x * move_speed, target_accel * delta)
else:
velocity.x = move_toward(velocity.x, 0, target_friction * delta)
move_and_slide()
## EXPERT USAGE:
## Adjust 'Jump Height' and 'Time To Peak' in inspector to tune feel.
## Gravity is calculated automatically from these values.
# coyote_timer.gd
extends Node
class_name CoyoteTimer
# Coyote Time Logic
# Tracks the precise time since the player left the floor to grant a brief jump window.
var time_left: float = 0.0
const MAX_COYOTE: float = 0.15
func update_coyote(is_on_floor: bool, delta: float) -> void:
if is_on_floor:
time_left = MAX_COYOTE
else:
time_left -= delta
func can_jump() -> bool:
# Pattern: Avoid exact floating point equality (== 0.0).
return time_left > 0.0
# custom_collision_slider.gd
extends CharacterBody2D
class_name CustomCollisionSlider
# Custom Collision Slide Response
# Manual calculation of sliding response for high-speed or non-standard physics.
func _physics_process(delta: float) -> void:
# Use move_and_collide for manual control.
var collision := move_and_collide(velocity * delta)
if collision:
# Expert Pattern: Manually slide along the normal to prevent sticking.
velocity = velocity.slide(collision.get_normal())
# fast_projectile_ccd.gd
extends RigidBody2D
class_name FastProjectileCCD
# Continuous Collision Detection (CCD) for High-Speed Sprites
# Prevents "tunneling" through thin geometry.
func _ready() -> void:
# Pattern: Use ray-casting CD for extremely fast, small bullets/sprites.
continuous_cd = RigidBody2D.CCD_MODE_CAST_RAY
max_contacts_reported = 1
contact_monitor = true
# jump_buffer.gd
extends Node
class_name JumpBuffer
# Frame-Perfect Jump Buffering via Unhandled Input
# Captures input outside the physics tick to ensure fast inputs aren't dropped.
var buffer_time_left: float = 0.0
const MAX_BUFFER: float = 0.1
func _unhandled_input(event: InputEvent) -> void:
# Pattern: Use StringName (&"jump") for optimized input checks.
if event.is_action_pressed(&"jump"):
buffer_time_left = MAX_BUFFER
func _physics_process(delta: float) -> void:
if buffer_time_left > 0.0:
buffer_time_left -= delta
func consume_jump() -> bool:
if buffer_time_left > 0.0:
buffer_time_left = 0.0
return true
return false
# ledge_grab_sensor.gd
extends Node2D
class_name LedgeGrabSensor
# Ledge Grabbing using ShapeCast2D logic
# Performs a nodeless shape query to detect precise ledge geometry.
@export var body: CharacterBody2D
func check_ledge() -> bool:
var space_state := get_world_2d().direct_space_state
# Create an on-demand circle shape for detection.
var shape := CircleShape2D.new()
shape.radius = 4.0
var query := PhysicsShapeQueryParameters2D.new()
query.shape = shape
query.transform = global_transform
query.exclude = [body.get_rid()]
var hits := space_state.intersect_shape(query)
return not hits.is_empty()
extends CharacterBody2D
class_name OneWayDropHandler
## Expert One-Way Drop Logic (Godot 4.6).
## Down + Jump intentionally drops through platforms.
const PLATFORM_LAYER = 4 # Example layer for one-way platforms
func _physics_process(_delta: float) -> void:
if Input.is_action_pressed("down") and Input.is_action_just_pressed("jump"):
_drop_through()
func _drop_through() -> void:
# Temporarily disable the collision mask for the platform layer
set_collision_mask_value(PLATFORM_LAYER, false)
# Restore after a short delay
await get_tree().create_timer(0.2).timeout
set_collision_mask_value(PLATFORM_LAYER, true)
## [SKILL NOTICE]: Use 'set_collision_mask_value()' to temporarily ignore
## one-way platform layers. Use 'await' for precise, non-blocking timing.
# platformer_animation_sync.gd
extends Node
class_name PlatformerAnimationSync
# State-Driven AnimationTree Sync
# Safely updates logic conditions for AnimationTree without negation operators.
@export var anim_tree: AnimationTree
@export var character: CharacterBody2D
func _physics_process(_delta: float) -> void:
if not anim_tree or not character: return
var is_moving := abs(character.velocity.x) > 10.0
var is_in_air := not character.is_on_floor()
# Pattern: Explicitly set booleans for logic-safe Advance Conditions.
anim_tree.set("parameters/conditions/is_moving", is_moving)
anim_tree.set("parameters/conditions/is_idle", not is_moving)
anim_tree.set("parameters/conditions/is_airborne", is_in_air)
anim_tree.set("parameters/conditions/is_on_ground", not is_in_air)
# skills/genre-platformer/scripts/platformer_camera.gd
extends Camera2D
## Platformer Camera Expert Pattern
## "Look Ahead" camera that smooths movement and anticipates player direction.
class_name PlatformerCamera
@export var target: Node2D
@export var look_ahead_dist: float = 100.0
@export var lerp_speed: float = 3.0
@export var vertical_offset: float = -50.0
var _current_look_ahead_x: float = 0.0
func _physics_process(delta: float) -> void:
if not target: return
# 1. Base Position
var target_pos = target.global_position
target_pos.y += vertical_offset
# 2. Look Ahead Logic
if target is CharacterBody2D:
var v_x = target.velocity.x
if abs(v_x) > 10.0:
var dir = sign(v_x)
_current_look_ahead_x = lerp(_current_look_ahead_x, dir * look_ahead_dist, lerp_speed * delta)
else:
# Center when stopped
_current_look_ahead_x = lerp(_current_look_ahead_x, 0.0, lerp_speed * delta)
target_pos.x += _current_look_ahead_x
# 3. Position Smoothing (or use Camera2D built-in)
# We apply manually for more control if position_smoothing is off
global_position = global_position.lerp(target_pos, 5.0 * delta)
## EXPERT USAGE:
## Assign Player to 'target'. Ensure Camera2D is NOT child of player
## (or use RemoteTransform2D) to avoid jitter.
extends CharacterBody2D
class_name ExpertPlatformerController
## Expert Platformer Physics (Godot 4.6).
## Implements Coyote Time and Jump Buffering for perfect feel.
@export var speed: float = 300.0
@export var jump_velocity: float = -400.0
@export var coyote_time: float = 0.15
@export var jump_buffer: float = 0.15
var gravity: float = ProjectSettings.get_setting("physics/2d/default_gravity")
var _coyote_timer: float = 0.0
var _buffer_timer: float = 0.0
func _physics_process(delta: float) -> void:
# Add the gravity.
if not is_on_floor():
velocity.y += gravity * delta
_coyote_timer += delta
else:
_coyote_timer = 0.0
# Handle Jump Buffer
if Input.is_action_just_pressed("jump"):
_buffer_timer = jump_buffer
_buffer_timer -= delta
# Handle Jump logic
if _buffer_timer > 0 and _coyote_timer < coyote_time:
velocity.y = jump_velocity
_buffer_timer = 0
_coyote_timer = coyote_time # Consume coyote time
# Get the input direction and handle the movement/deceleration.
var direction := Input.get_axis("left", "right")
if direction:
velocity.x = lerpf(velocity.x, direction * speed, 0.2)
else:
velocity.x = move_toward(velocity.x, 0, speed)
move_and_slide()
## [SKILL NOTICE]: Use 'lerpf' for movement smoothing and 'move_and_slide()'
## for standard CharacterBody2D interaction. Time-based buffering is mandatory.
# player_ground_controller.gd
extends CharacterBody2D
class_name PlayerGroundController
# Advanced Ground Movement with Slope Physics
# Configures a CharacterBody2D for constant slope speed and floor snapping.
@export var speed: float = 300.0
var gravity: float = ProjectSettings.get_setting("physics/2d/default_gravity")
func _ready() -> void:
# Expert Tip: Enable constant speed on slopes to prevent "launching" off peaks.
floor_constant_speed = true
floor_max_angle = deg_to_rad(45.0)
floor_snap_length = 8.0 # Snap to ground even when running down slopes.
func _physics_process(delta: float) -> void:
# NEVER multiply velocity by delta before move_and_slide().
velocity.y += gravity * delta
var input_dir := Input.get_axis(&"ui_left", &"ui_right")
velocity.x = input_dir * speed
move_and_slide()
# synchronized_platform.gd
extends AnimatableBody2D
class_name SynchronizedPlatform
# Synchronized Moving Platform
# Ensures platforms move flawlessly in sync with the physics tick.
func _ready() -> void:
# Pattern: Mandatory for platforms moved by AnimationPlayer/Tweens.
sync_to_physics = true
# variable_jump.gd
extends Node
class_name VariableJump
# Variable Jump Height (Early Release Cutoff)
# Cuts upward momentum if the jump button is released mid-ascent.
@export var body: CharacterBody2D
@export var min_jump_velocity: float = -200.0
func _physics_process(_delta: float) -> void:
# Pattern: Only cut if moving UP and button just released.
if Input.is_action_just_released(&"jump") and body.velocity.y < min_jump_velocity:
body.velocity.y = min_jump_velocity
# wall_slide_sensor.gd
extends Node2D
class_name WallSlideSensor
# Wall Sliding via Nodeless Physics Raycast
# Direct query using PhysicsServer2D for zero-latency detection without nodes.
@export var body: CharacterBody2D
func check_wall(look_direction: float) -> bool:
var space_state := get_world_2d().direct_space_state
# Cast ray slightly outside the collision shape.
var ray_end := global_position + Vector2(look_direction * 15.0, 0)
var query := PhysicsRayQueryParameters2D.create(global_position, ray_end)
query.exclude = [body.get_rid()]
var result := space_state.intersect_ray(query)
return not result.is_empty()