godot-characterbody-2d

📁 thedivergentai/gd-agentic-skills 📅 3 days ago
0
总安装量
5
周安装量
安装命令
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-characterbody-2d

Agent 安装分布

opencode 5
gemini-cli 5
codex 5
github-copilot 4
claude-code 3

Skill 文档

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