godot-characterbody-2d

Expert patterns for CharacterBody2D including platformer movement (coyote time, jump buffering, variable jump height), top-down movement (8-way, tank controls), collision handling, one-way platforms, and state machines. Use for player characters, NPCs, or enemies. Trigger keywords: CharacterBody2D, move_and_slide, is_on_floor, coyote_time, jump_buffer, velocity, get_slide_collision, one_way_platforms, state_machine.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "godot-characterbody-2d" with this command: npx skills add thedivergentai/gd-agentic-skills/thedivergentai-gd-agentic-skills-godot-characterbody-2d

CharacterBody2D Implementation

Expert guidance for player-controlled 2D movement using Godot's physics system.

NEVER Do

  • NEVER multiply velocity by delta when using move_and_slide()move_and_slide() already accounts for delta time. Multiplying causes slow, frame-dependent movement.
  • NEVER forget to check is_on_floor() before jump — Allows mid-air jumps without double-jump mechanic.
  • NEVER use velocity.x = direction * SPEED without friction — Character slides infinitely without friction in else branch. Use move_toward(velocity.x, 0, FRICTION * delta).
  • NEVER set velocity.y to exact value when falling — Overwrites gravity accumulation. Use velocity.y += gravity * delta instead of velocity.y = gravity.
  • NEVER use floor_snap_length > 16px — Large snap values cause "sticking" to slopes and walls.

Available Scripts

MANDATORY: Read the appropriate script before implementing the corresponding pattern.

expert_physics_2d.gd

Complete platformer movement with coyote time, jump buffering, smooth acceleration/friction, and sub-pixel stabilization. Uses move_toward for precise control.

dash_controller.gd

Frame-perfect dash with I-frames, cooldown, and momentum preservation.

wall_jump_controller.gd

Wall slide, cling, and directional wall jump with auto-correction.

Do First: Read expert_physics_2d.gd for platformer foundation before adding dash/wall-jump.


When to Use CharacterBody2D

Use CharacterBody2D For:

  • Player characters (platformer, top-down, side-scroller)
  • NPCs with custom movement logic
  • Enemies with non-physics-based movement

Use RigidBody2D For:

  • Physics-driven objects (rolling boulders, vehicles)
  • Objects affected by forces and impulses

Platformer Movement Pattern

Basic Platformer Controller

extends CharacterBody2D

const SPEED := 300.0
const JUMP_VELOCITY := -400.0

# Get the gravity from the project settings
var gravity: int = ProjectSettings.get_setting("physics/2d/default_gravity")

func _physics_process(delta: float) -> void:
    # Apply gravity
    if not is_on_floor():
        velocity.y += gravity * delta
    
    # Handle jump
    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = JUMP_VELOCITY
    
    # Get input direction
    var direction := Input.get_axis("move_left", "move_right")
    
    # Apply movement
    if direction:
        velocity.x = direction * SPEED
    else:
        velocity.x = move_toward(velocity.x, 0, SPEED)
    
    move_and_slide()

Advanced Platformer with Coyote Time & Jump Buffer

extends CharacterBody2D

const SPEED := 300.0
const JUMP_VELOCITY := -400.0
const ACCELERATION := 1500.0
const FRICTION := 1200.0
const AIR_RESISTANCE := 200.0

# Coyote time: grace period after leaving platform
const COYOTE_TIME := 0.1
var coyote_timer := 0.0

# Jump buffering: remember jump input slightly before landing
const JUMP_BUFFER_TIME := 0.1
var jump_buffer_timer := 0.0

var gravity: int = ProjectSettings.get_setting("physics/2d/default_gravity")

func _physics_process(delta: float) -> void:
    # Gravity
    if not is_on_floor():
        velocity.y += gravity * delta
        coyote_timer -= delta
    else:
        coyote_timer = COYOTE_TIME
    
    # Jump buffering
    if Input.is_action_just_pressed("jump"):
        jump_buffer_timer = JUMP_BUFFER_TIME
    else:
        jump_buffer_timer -= delta
    
    # Jump (with coyote time and buffer)
    if jump_buffer_timer > 0 and coyote_timer > 0:
        velocity.y = JUMP_VELOCITY
        jump_buffer_timer = 0
        coyote_timer = 0
    
    # Variable jump height
    if Input.is_action_just_released("jump") and velocity.y < 0:
        velocity.y *= 0.5
    
    # Movement with acceleration/friction
    var direction := Input.get_axis("move_left", "move_right")
    
    if direction:
        velocity.x = move_toward(velocity.x, direction * SPEED, ACCELERATION * delta)
    else:
        var friction_value := FRICTION if is_on_floor() else AIR_RESISTANCE
        velocity.x = move_toward(velocity.x, 0, friction_value * delta)
    
    move_and_slide()

Top-Down Movement Pattern

8-Directional Top-Down

extends CharacterBody2D

const SPEED := 200.0
const ACCELERATION := 1500.0
const FRICTION := 1000.0

func _physics_process(delta: float) -> void:
    # Get input direction (normalized for diagonal movement)
    var input_vector := Input.get_vector(
        "move_left", "move_right",
        "move_up", "move_down"
    )
    
    if input_vector != Vector2.ZERO:
        # Accelerate toward target velocity
        velocity = velocity.move_toward(
            input_vector * SPEED,
            ACCELERATION * delta
        )
    else:
        # Apply friction
        velocity = velocity.move_toward(
            Vector2.ZERO,
            FRICTION * delta
        )
    
    move_and_slide()

Top-Down with Rotation (Tank Controls)

extends CharacterBody2D

const SPEED := 200.0
const ROTATION_SPEED := 3.0

func _physics_process(delta: float) -> void:
    # Rotation
    var rotate_direction := Input.get_axis("rotate_left", "rotate_right")
    rotation += rotate_direction * ROTATION_SPEED * delta
    
    # Forward/backward movement
    var move_direction := Input.get_axis("move_backward", "move_forward")
    velocity = transform.x * move_direction * SPEED
    
    move_and_slide()

Collision Handling

Detecting Floor/Walls/Ceiling

func _physics_process(delta: float) -> void:
    move_and_slide()
    
    if is_on_floor():
        print("Standing on ground")
    
    if is_on_wall():
        print("Touching wall")
    
    if is_on_ceiling():
        print("Hitting ceiling")

Get Collision Information

func _physics_process(delta: float) -> void:
    move_and_slide()
    
    # Process each collision
    for i in get_slide_collision_count():
        var collision := get_slide_collision(i)
        print("Collided with: ", collision.get_collider().name)
        print("Collision normal: ", collision.get_normal())
        
        # Example: bounce off walls
        if collision.get_collider().is_in_group("bouncy"):
            velocity = velocity.bounce(collision.get_normal())

One-Way Platforms

extends CharacterBody2D

func _physics_process(delta: float) -> void:
    # Allow falling through platforms by pressing down
    if Input.is_action_pressed("move_down") and is_on_floor():
        position.y += 1  # Move slightly down to pass through
    
    velocity.y += gravity * delta
    move_and_slide()

Movement States with State Machine

extends CharacterBody2D

enum State { IDLE, RUNNING, JUMPING, FALLING, DASHING }

var current_state := State.IDLE
var dash_velocity := Vector2.ZERO
const DASH_SPEED := 600.0
const DASH_DURATION := 0.2
var dash_timer := 0.0

func _physics_process(delta: float) -> void:
    match current_state:
        State.IDLE:
            _state_idle(delta)
        State.RUNNING:
            _state_running(delta)
        State.JUMPING:
            _state_jumping(delta)
        State.FALLING:
            _state_falling(delta)
        State.DASHING:
            _state_dashing(delta)

func _state_idle(delta: float) -> void:
    velocity.x = move_toward(velocity.x, 0, FRICTION * delta)
    
    if Input.is_action_pressed("move_left") or Input.is_action_pressed("move_right"):
        current_state = State.RUNNING
    elif Input.is_action_just_pressed("jump"):
        current_state = State.JUMPING
    
    move_and_slide()

func _state_dashing(delta: float) -> void:
    dash_timer -= delta
    velocity = dash_velocity
    
    if dash_timer <= 0:
        current_state = State.IDLE
    
    move_and_slide()

Best Practices

1. Use Constants for Tuning

# ✅ Good - easy to tweak
const SPEED := 300.0
const JUMP_VELOCITY := -400.0

# ❌ Bad - magic numbers
velocity.x = 300
velocity.y = -400

2. Use @export for Designer Control

@export var speed: float = 300.0
@export var jump_velocity: float = -400.0
@export_range(0, 2000) var acceleration: float = 1500.0

3. Separate Movement from Animation

func _physics_process(delta: float) -> void:
    _handle_movement(delta)
    _handle_animation()
    move_and_slide()

func _handle_movement(delta: float) -> void:
    # Movement logic only
    pass

func _handle_animation() -> void:
    # Animation state changes only
    if velocity.x > 0:
        $AnimatedSprite2D.flip_h = false
    elif velocity.x < 0:
        $AnimatedSprite2D.flip_h = true

4. Use Floor Detection Parameters

func _ready() -> void:
    # Set floor parameters
    floor_max_angle = deg_to_rad(45)  # Max slope angle
    floor_snap_length = 8.0  # Distance to snap to floor
    motion_mode = MOTION_MODE_GROUNDED  # Vs MOTION_MODE_FLOATING

Common Gotchas

Issue: Character slides on slopes

# Solution: Increase friction
const FRICTION := 1200.0

Issue: Character stutters on moving platforms

# Solution: Enable platform snap
func _physics_process(delta: float) -> void:
    move_and_slide()
    
    # Snap to platform velocity
    if is_on_floor():
        var floor_velocity := get_platform_velocity()
        velocity += floor_velocity

Issue: Double jump exploit

# Solution: Track if jump was used
var can_jump := true

func _physics_process(delta: float) -> void:
    if is_on_floor():
        can_jump = true
    
    if Input.is_action_just_pressed("jump") and can_jump:
        velocity.y = JUMP_VELOCITY
        can_jump = false

Reference

Related

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

godot-master

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

godot-shaders-basics

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

godot-ui-theming

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

godot-particles

No summary provided by upstream source.

Repository SourceNeeds Review