返回 Skills 目录
gamedev-skills/awesome-gamedev-agent-skills已通过检查

SKILL DETAIL

godot-2d-movement

gamedev-skills/awesome-gamedev-agent-skills/godot-2d-movement

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.

安装量 · 284查看来源

Installation

npx skills add https://github.com/gamedev-skills/awesome-gamedev-agent-skills --skill godot-2d-movement

技能文件

SKILL.md

最近同步 · 2026年10月22日

references/controller-recipes.md›
# 2D controller recipes (Godot 4.7)

Depth companion to `godot-2d-movement`.

## Variable jump height (release to cut the jump short)

```gdscript
func _physics_process(delta: float) -> void:
    if not is_on_floor():
        velocity.y += gravity * delta
    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = jump_velocity
    # Releasing jump early dampens upward velocity -> shorter hop.
    if Input.is_action_just_released("jump") and velocity.y < 0.0:
        velocity.y *= 0.5
    # ... horizontal + move_and_slide()
```

## Jump buffering (press slightly before landing still jumps)

```gdscript
@export var buffer_time := 0.1
var _buffer := 0.0

func _physics_process(delta: float) -> void:
    if Input.is_action_just_pressed("jump"):
        _buffer = buffer_time
    else:
        _buffer = max(_buffer - delta, 0.0)

    if _buffer > 0.0 and is_on_floor():
        velocity.y = jump_velocity
        _buffer = 0.0
    # ... gravity + horizontal + move_and_slide()
```

## One-way platforms

On the platform's `CollisionShape2D` / static body, enable **one-way collision** in the
editor (the shape gets a one-way arrow). The `CharacterBody2D` passes through from one
side. To drop through deliberately, briefly disable the body's collision with that layer
or use `set_collision_mask_value()` for a few frames.

## Moving platforms

Make the platform an `AnimatableBody2D` (moves via animation/code, pushes bodies) and
animate it with an `AnimationPlayer` or a `Tween`. A `CharacterBody2D` standing on it
inherits the platform's velocity automatically through `move_and_slide()` (controlled by
`platform_floor_layers` and `platform_on_leave`).

## move_and_collide() — manual collision response

When you want full control (custom bounce, no sliding), use `move_and_collide()`:

```gdscript
func _physics_process(delta: float) -> void:
    var motion := velocity * delta                 # here you DO scale by delta
    var collision := move_and_collide(motion)
    if collision:
        # Reflect velocity off the surface normal (a simple bounce).
        velocity = velocity.bounce(collision.get_normal())
```

`KinematicCollision2D` exposes `get_normal()`, `get_collider()`, `get_position()`,
`get_remainder()`, and `get_travel()`.

## Acceleration-based feel

Instead of snapping `velocity.x` to `dir * speed`, ramp it for weightier movement:

```gdscript
@export var accel := 1500.0
@export var friction := 1800.0

func _physics_process(delta: float) -> void:
    var dir := Input.get_axis("move_left", "move_right")
    if dir != 0.0:
        velocity.x = move_toward(velocity.x, dir * speed, accel * delta)
    else:
        velocity.x = move_toward(velocity.x, 0.0, friction * delta)
    move_and_slide()
```

## Useful CharacterBody2D properties

- `floor_max_angle` (rad) — steepest slope still treated as floor (default 45°).
- `floor_snap_length` — keeps the body glued to downward slopes.
- `floor_stop_on_slope` — prevents sliding when idle on a slope.
- `max_slides` — iteration cap per `move_and_slide()` (default 4).
- `up_direction` — defines floor/wall/ceiling; `Vector2.UP` for side-scrollers.
- `slide_on_ceiling` — slide vs. stop when hitting a ceiling mid-jump.
SKILL.md›
---
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)

```gdscript
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

```gdscript
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

```gdscript
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)

```gdscript
@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.