godot-2d-movement

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Godot 2D Movement (4.x)

Godot 2D 角色移动(4.x版本)

Build responsive 2D character controllers with
CharacterBody2D
and the argument-less
move_and_slide()
. Targets Godot 4.3+.
使用
CharacterBody2D
和无参数的
move_and_slide()
构建响应式2D角色控制器。适配**Godot 4.3+**版本。

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
.
  • 适用于编写可移动并发生碰撞的2D玩家/敌人控制器:平台类(重力+跳跃)或俯视角(自由8方向)角色、斜坡行走,以及墙体/地面检测。
  • 适用于
    move_and_slide()
    “无作用”、角色穿过地面下落,或
    is_on_floor()
    始终返回false的情况。
不适用场景: 动态刚体、区域、射线检测、碰撞层 → 参考
godot-physics
;瓦片地图关卡 → 参考
godot-tilemap
;完整平台游戏模板 → 参考
platformer
类型技能;引擎无关的手感调校 → 参考
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.
  1. **使用
    CharacterBody2D
    **并添加
    CollisionShape2D
    子节点。它由脚本驱动:自身不会下落或对力做出反应。
  2. 设置
    velocity
    属性,然后调用
    move_and_slide()
    (4.x版本无需参数)。该方法会读取
    velocity
    、移动物体、沿表面滑动,并更新
    velocity
    以反映实际运动情况。
  3. _physics_process(delta)
    中执行
    ——
    move_and_slide()
    内部已考虑物理步骤的delta,因此无需自行将
    velocity
    乘以delta。
  4. 平台类角色: 每帧向
    velocity.y
    添加重力,当
    is_on_floor()
    为true时,将
    velocity.y
    设为负值实现跳跃。
  5. 俯视角角色: 根据输入构建方向向量并按速度缩放;设置
    motion_mode = MOTION_MODE_FLOATING
    ,这样就不会区分“地面/墙体”。
  6. 读取结果:调用
    move_and_slide()
    后,使用
    is_on_floor()
    is_on_wall()
    get_wall_normal()
    get_slide_collision(i)
    获取运动结果。

Patterns

实现模式

1. Platformer controller (gravity, jump, slopes)

1. 平台类控制器(重力、跳跃、斜坡)

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

@export var speed := 200.0
@export var jump_velocity := -400.0
@export var gravity := 1200.0          # 像素/秒²(可根据手感调整)

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

    # 水平输入:-1、0或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)   # 减速至停止

    move_and_slide()   # 4.x版本:无需参数;会使用并更新`velocity`

2. Top-down 8-direction movement

2. 俯视角8方向移动

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()
gdscript
extends CharacterBody2D

@export var speed := 220.0

func _ready() -> void:
    motion_mode = CharacterBody2D.MOTION_MODE_FLOATING  # 无地面/天花板概念

func _physics_process(_delta: float) -> void:
    # get_vector根据四个输入动作返回一个近似归一化的Vector2。
    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

3. 移动后读取滑动碰撞

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
gdscript
func _physics_process(delta: float) -> void:
    # ... 设置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)        # 移动时触碰到敌人

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

4. 郊狼时间(离开边缘后短时间内仍可跳跃)

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() ...
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
    # ... 水平输入 + 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.
  • 3.x版本的调用方式已移除。
    move_and_slide(velocity)
    (返回新velocity)已被移除。在4.x版本中,需设置
    velocity
    属性后调用无参数的
    move_and_slide()
    move_and_slide(velocity, Vector2.UP)
    无法被解析。
  • 将velocity乘以delta。
    move_and_slide()
    已考虑物理delta。设置
    velocity = dir * speed * delta
    会导致角色移动极慢。
  • _process
    中处理移动
    而非
    _physics_process
    会导致移动依赖帧率、出现抖动。务必在
    _physics_process
    中处理移动。
  • is_on_floor()
    始终返回false
    :可能是
    up_direction
    设置错误(默认是
    Vector2.UP
    )、缺少
    CollisionShape2D
    ,或是本帧未调用
    move_and_slide()
  • 穿过地面下落:通常意味着碰撞形状缺失/尺寸为0、地面物体所在的图层未被当前物体的碰撞掩码包含,或是通过
    position +=
    而非velocity +
    move_and_slide()
    移动物体。
  • ** idle时沿斜坡下滑:** 保持
    floor_stop_on_slope = true
    (默认值)并设置
    floor_snap_length
    ,使角色能贴合向下的斜坡。

References

参考资料

  • For one-way platforms, moving platforms, jump buffering, variable jump height, and
    move_and_collide()
    (manual collision response), read
    references/controller-recipes.md
    .
  • 如需了解单向平台、移动平台、跳跃缓冲、可变跳跃高度以及
    move_and_collide()
    (手动碰撞响应),请阅读
    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.
  • godot-physics
    —— 碰撞层/掩码、区域、射线检测、刚体。
  • godot-tilemap
    —— 构建角色行走的关卡。
  • godot-animation
    —— 根据移动状态驱动精灵/骨骼动画。
  • camera-systems
    —— 跟踪角色的跟随相机、死区和前瞻功能。
  • platformer
    /
    input-systems
    —— 完整类型模板和可重新绑定的输入系统。