PluginBench
Skill
Pass
Audit score 90

godot-2d-movement

gamedev-skills/awesome-gamedev-agent-skills

Build responsive 2D character controllers in Godot 4.x with CharacterBody2D and move_and_slide().

What is godot-2d-movement?

Implement 2D kinematic movement for platformers and top-down games using Godot 4.7's CharacterBody2D. Covers gravity, jumping, slope handling, collision detection, and 8-direction motion. Use this when scripting player or enemy controllers that need physics-based movement without rigid bodies.

  • Apply gravity and implement jump mechanics for platformer characters
  • Handle 8-direction top-down movement with floating motion mode
  • Detect floor, wall, and collision states using is_on_floor() and get_slide_collision()
  • Slide along slopes and surfaces naturally with move_and_slide()
  • Implement coyote time and other jump-feel refinements
  • Read collision results to trigger damage, pickups, or other interactions

How to install godot-2d-movement

npx skills add https://github.com/gamedev-skills/awesome-gamedev-agent-skills --skill godot-2d-movement
Prerequisites
  • Godot 4.7 installed
  • A scene with a CharacterBody2D node and a CollisionShape2D child
  • Input actions defined in Project Settings > Input Map (e.g., move_left, move_right, jump)
Claude Code
Cursor
Windsurf
Cline

How to use godot-2d-movement

  1. 1.Create a CharacterBody2D node with a CollisionShape2D child in your scene
  2. 2.Attach a GDScript to the CharacterBody2D
  3. 3.In _physics_process(delta), set the velocity property based on input and physics (gravity, jump)
  4. 4.Call move_and_slide() with no arguments to move the body and update velocity
  5. 5.Use is_on_floor(), is_on_wall(), and get_slide_collision() to read movement results
  6. 6.Tune exported variables like speed, jump_velocity, and gravity to match your game feel

Use cases

Good for
  • Scripting a platformer player controller with gravity, jump, and slope walking
  • Building a top-down character that moves in 8 directions without gravity
  • Detecting when a moving character touches enemies or hazards
  • Fixing move_and_slide() behavior when characters sink through floors or is_on_floor() returns false
  • Adding forgiving jump timing with coyote time mechanics
Who it's for
  • Game developers building 2D platformers or top-down games in Godot 4.x
  • Programmers scripting player and enemy controllers
  • Developers debugging character movement or collision issues in Godot

godot-2d-movement FAQ

Why does my character sink through the floor?

Check that the CollisionShape2D exists and is not zero-sized, the floor body is on a collision layer this character's mask includes, and you are moving via velocity + move_and_slide() rather than position +=.

Why is is_on_floor() always false?

Verify that up_direction is set correctly (default Vector2.UP), a CollisionShape2D child exists, and you called move_and_slide() this frame. Also check collision layers and masks match.

Should I multiply velocity by delta in _physics_process?

No. move_and_slide() already accounts for the physics delta internally. Multiplying by delta makes the character crawl. Set velocity directly and let move_and_slide() handle the frame timing.

How do I implement a jump that feels forgiving?

Use coyote time: track a countdown that resets when is_on_floor() is true, and allow jumping if the countdown is still positive. This lets players jump briefly after leaving a ledge.

What is the difference between platformer and top-down movement?

Platformer uses gravity (velocity.y increases each frame) and jump input; top-down sets motion_mode to MOTION_MODE_FLOATING and builds velocity from 8-direction input without gravity.

Full instructions (SKILL.md)

Source of truth, from gamedev-skills/awesome-gamedev-agent-skills.


name: godot-2d-movement description: > Implement 2D kinematic character movement in Godot 4.7 with CharacterBody2D and move_and_slide(): platformer run/jump with gravity, top-down 8-direction motion, slope handling, and reading collisions. Use when coding a 2D player or enemy controller, a platformer or top-down character, or fixing move_and_slide()/ is_on_floor() behavior in a .tscn with a CharacterBody2D.

Godot 2D Movement (4.x)

Build responsive 2D character controllers with CharacterBody2D and the argument-less move_and_slide(). Targets Godot 4.7.

When to use

  • Use when scripting a 2D player/enemy that moves and collides: platformer (gravity + jump) or top-down (free 8-direction), slope walking, or wall/floor detection.
  • Use when move_and_slide() "does nothing", the character sinks through floors, or is_on_floor() is always false.

When not to use: dynamic rigid bodies, areas, raycasts, collision layers → godot-physics; tile-based levels → godot-tilemap; full platformer game template → the platformer genre skill; engine-agnostic feel tuning → physics-tuning.

Core workflow

  1. Use CharacterBody2D with a CollisionShape2D child. It is script-driven: it does not fall or react to forces on its own.
  2. Set the velocity property, then call move_and_slide() (no arguments in 4.x). The method reads velocity, moves the body, slides along surfaces, and updates velocity to reflect what actually happened.
  3. Do it in _physics_process(delta) — move_and_slide() uses the physics step's delta internally, so do not multiply velocity by delta yourself.
  4. Platformer: add gravity to velocity.y each tick, jump by setting velocity.y negative when is_on_floor().
  5. Top-down: build a direction from input and scale by speed; set motion_mode = MOTION_MODE_FLOATING so there is no "floor/wall" distinction.
  6. Read results with is_on_floor(), is_on_wall(), get_wall_normal(), and get_slide_collision(i) after the call.

Patterns

1. Platformer controller (gravity, jump, slopes)

extends CharacterBody2D

@export var speed := 200.0
@export var jump_velocity := -400.0
@export var gravity := 1200.0          # pixels/sec^2 (tune to taste)

func _physics_process(delta: float) -> void:
    # Apply gravity while airborne.
    if not is_on_floor():
        velocity.y += gravity * delta

    # Jump only when grounded (Input action set in Project Settings > Input Map).
    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = jump_velocity

    # Horizontal input: -1, 0, or 1.
    var dir := Input.get_axis("move_left", "move_right")
    if dir != 0.0:
        velocity.x = dir * speed
    else:
        velocity.x = move_toward(velocity.x, 0.0, speed)   # decelerate to a stop

    move_and_slide()   # 4.x: no arguments; uses and updates `velocity`

2. Top-down 8-direction movement

extends CharacterBody2D

@export var speed := 220.0

func _ready() -> void:
    motion_mode = CharacterBody2D.MOTION_MODE_FLOATING  # no floor/ceiling concept

func _physics_process(_delta: float) -> void:
    # get_vector returns a normalized-ish Vector2 from four input actions.
    var input := Input.get_vector("move_left", "move_right", "move_up", "move_down")
    velocity = input * speed
    move_and_slide()

3. Reading slide collisions after moving

func _physics_process(delta: float) -> void:
    # ... set velocity ...
    move_and_slide()
    for i in get_slide_collision_count():
        var c := get_slide_collision(i)
        var other := c.get_collider()
        if other and other.is_in_group("enemies"):
            take_damage(1)        # touched an enemy while moving

4. Coyote time (forgiving jump just after leaving a ledge)

@export var coyote_time := 0.1
var _coyote := 0.0

func _physics_process(delta: float) -> void:
    if not is_on_floor():
        velocity.y += gravity * delta
        _coyote -= delta
    else:
        _coyote = coyote_time

    if Input.is_action_just_pressed("jump") and _coyote > 0.0:
        velocity.y = jump_velocity
        _coyote = 0.0
    # ... horizontal input + move_and_slide() ...

Pitfalls

  • 3.x signature is gone. move_and_slide(velocity) (returning the new velocity) was removed. In 4.x set the velocity property and call move_and_slide() with no args. move_and_slide(velocity, Vector2.UP) will not parse.
  • Multiplying velocity by delta. move_and_slide() already accounts for the physics delta. Setting velocity = dir * speed * delta makes the body crawl.
  • Movement in _process instead of _physics_process causes frame-rate-dependent, jittery motion. Always move in _physics_process.
  • is_on_floor() always false when up_direction is wrong (default Vector2.UP), there is no CollisionShape2D, or you never called move_and_slide() this frame.
  • Sinking through floors usually means the collision shape is missing/zero-sized, the floor body is on a layer this body's mask doesn't include, or you moved the body via position += instead of velocity + move_and_slide().
  • Slope slipping while idle: keep floor_stop_on_slope = true (default) and set a floor_snap_length so the body sticks to downward slopes.

References

  • For one-way platforms, moving platforms, jump buffering, variable jump height, and move_and_collide() (manual collision response), read references/controller-recipes.md.

Related skills

  • godot-physics — collision layers/masks, areas, raycasts, rigid bodies.
  • godot-tilemap — building the levels this character walks on.
  • godot-animation — driving sprite/skeletal animation from movement state.
  • camera-systems — follow camera, deadzone, and look-ahead that track this character.
  • platformer / input-systems — full genre template and rebindable input.