godot-characterbody-2d

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

CharacterBody2D Implementation

CharacterBody2D 实现

Expert guidance for player-controlled 2D movement using Godot's physics system.
基于Godot物理系统的玩家可控2D移动专家指南。

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.

  • 使用
    move_and_slide()
    时,切勿将velocity乘以delta
    move_and_slide()
    已经考虑了delta时间,相乘会导致移动缓慢且依赖帧率。
  • 跳跃前切勿忘记检查
    is_on_floor()
    — 会导致没有二段跳机制却能在空中跳跃。
  • 切勿在无摩擦的情况下使用
    velocity.x = direction * SPEED
    — 角色会在else分支中无限滑动。请使用
    move_toward(velocity.x, 0, FRICTION * delta)
  • 下落时切勿将
    velocity.y
    设为固定值
    — 这会覆盖重力的累积效果。请使用
    velocity.y += gravity * delta
    而非
    velocity.y = gravity
  • 切勿将floor_snap_length设为大于16px的值 — 较大的吸附值会导致角色“粘”在斜坡和墙上。

Available Scripts

可用脚本

MANDATORY: Read the appropriate script before implementing the corresponding pattern.
强制要求:在实现对应模式前,请先阅读相应的脚本。

expert_physics_2d.gd

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.
完整的平台跳跃移动实现,包含coyote time、跳跃缓冲、平滑加速/减速以及亚像素稳定。使用move_toward实现精准控制。

dash_controller.gd

dash_controller.gd

Frame-perfect dash with I-frames, cooldown, and momentum preservation.
帧完美的冲刺功能,包含无敌帧、冷却时间和动量保留。

wall_jump_controller.gd

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.

墙面滑动、吸附和可定向的墙跳功能,带自动修正。
优先操作:在添加冲刺/墙跳功能前,请先阅读expert_physics_2d.gd以掌握平台跳跃基础。

When to Use CharacterBody2D

何时使用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
适合使用CharacterBody2D的场景:
  • 玩家角色(平台跳跃、俯视角、横版卷轴)
  • 带有自定义移动逻辑的NPC
  • 非物理驱动的敌人
适合使用RigidBody2D的场景:
  • 物理驱动的物体(滚动的巨石、载具)
  • 受外力和冲量影响的物体

Platformer Movement Pattern

平台跳跃移动模式

Basic Platformer Controller

基础平台跳跃控制器

gdscript
extends CharacterBody2D

const SPEED := 300.0
const JUMP_VELOCITY := -400.0
gdscript
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()
undefined
var gravity: int = ProjectSettings.get_setting("physics/2d/default_gravity")
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

# 获取输入方向
var direction := Input.get_axis("move_left", "move_right")

# 应用移动
if direction:
    velocity.x = direction * SPEED
else:
    velocity.x = move_toward(velocity.x, 0, SPEED)

move_and_slide()
undefined

Advanced Platformer with Coyote Time & Jump Buffer

包含Coyote Time与跳跃缓冲的进阶平台跳跃

gdscript
extends CharacterBody2D

const SPEED := 300.0
const JUMP_VELOCITY := -400.0
const ACCELERATION := 1500.0
const FRICTION := 1200.0
const AIR_RESISTANCE := 200.0
gdscript
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

Coyote time:离开平台后的 grace period

const COYOTE_TIME := 0.1 var coyote_timer := 0.0
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()
undefined
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: # 重力 if not is_on_floor(): velocity.y += gravity * delta coyote_timer -= delta else: coyote_timer = COYOTE_TIME
# 跳跃缓冲
if Input.is_action_just_pressed("jump"):
    jump_buffer_timer = JUMP_BUFFER_TIME
else:
    jump_buffer_timer -= delta

# 跳跃(支持coyote time和缓冲)
if jump_buffer_timer > 0 and coyote_timer > 0:
    velocity.y = JUMP_VELOCITY
    jump_buffer_timer = 0
    coyote_timer = 0

# 可变跳跃高度
if Input.is_action_just_released("jump") and velocity.y < 0:
    velocity.y *= 0.5

# 带加速/减速的移动
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()
undefined

Top-Down Movement Pattern

俯视角移动模式

8-Directional Top-Down

八方向俯视角移动

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

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

func _physics_process(delta: float) -> void:
    # 获取输入方向(归一化以处理斜向移动)
    var input_vector := Input.get_vector(
        "move_left", "move_right",
        "move_up", "move_down"
    )
    
    if input_vector != Vector2.ZERO:
        # 向目标速度加速
        velocity = velocity.move_toward(
            input_vector * SPEED,
            ACCELERATION * delta
        )
    else:
        # 应用减速
        velocity = velocity.move_toward(
            Vector2.ZERO,
            FRICTION * delta
        )
    
    move_and_slide()

Top-Down with Rotation (Tank Controls)

带旋转的俯视角移动(坦克式操控)

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

const SPEED := 200.0
const ROTATION_SPEED := 3.0

func _physics_process(delta: float) -> void:
    # 旋转控制
    var rotate_direction := Input.get_axis("rotate_left", "rotate_right")
    rotation += rotate_direction * ROTATION_SPEED * delta
    
    # 前后移动
    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

检测地面/墙面/天花板

gdscript
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")
gdscript
func _physics_process(delta: float) -> void:
    move_and_slide()
    
    if is_on_floor():
        print("站在地面上")
    
    if is_on_wall():
        print("接触墙面")
    
    if is_on_ceiling():
        print"撞到天花板")

Get Collision Information

获取碰撞信息

gdscript
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())
gdscript
func _physics_process(delta: float) -> void:
    move_and_slide()
    
    # 处理每次碰撞
    for i in get_slide_collision_count():
        var collision := get_slide_collision(i)
        print("碰撞对象: ", collision.get_collider().name)
        print("碰撞法线: ", collision.get_normal())
        
        # 示例:从弹性物体反弹
        if collision.get_collider().is_in_group("bouncy"):
            velocity = velocity.bounce(collision.get_normal())

One-Way Platforms

单向平台

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

func _physics_process(delta: float) -> void:
    # 按下向下键时允许穿过平台
    if Input.is_action_pressed("move_down") and is_on_floor():
        position.y += 1  # 稍微向下移动以穿过平台
    
    velocity.y += gravity * delta
    move_and_slide()

Movement States with State Machine

基于状态机的移动状态管理

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

1. 使用常量进行参数调优

gdscript
undefined
gdscript
undefined

✅ Good - easy to tweak

✅ 良好实践 - 便于调整

const SPEED := 300.0 const JUMP_VELOCITY := -400.0
const SPEED := 300.0 const JUMP_VELOCITY := -400.0

❌ Bad - magic numbers

❌ 不良实践 - 魔法数值

velocity.x = 300 velocity.y = -400
undefined
velocity.x = 300 velocity.y = -400
undefined

2. Use
@export
for Designer Control

2. 使用
@export
让设计师可控参数

gdscript
@export var speed: float = 300.0
@export var jump_velocity: float = -400.0
@export_range(0, 2000) var acceleration: float = 1500.0
gdscript
@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

3. 将移动与动画分离

gdscript
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
gdscript
func _physics_process(delta: float) -> void:
    _handle_movement(delta)
    _handle_animation()
    move_and_slide()

func _handle_movement(delta: float) -> void:
    # 仅包含移动逻辑
    pass

func _handle_animation() -> void:
    # 仅包含动画状态切换
    if velocity.x > 0:
        $AnimatedSprite2D.flip_h = false
    elif velocity.x < 0:
        $AnimatedSprite2D.flip_h = true

4. Use Floor Detection Parameters

4. 设置地面检测参数

gdscript
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
gdscript
func _ready() -> void:
    # 设置地面参数
    floor_max_angle = deg_to_rad(45)  # 最大斜坡角度
    floor_snap_length = 8.0  # 地面吸附距离
    motion_mode = MOTION_MODE_GROUNDED  # 对比MOTION_MODE_FLOATING

Common Gotchas

常见问题

Issue: Character slides on slopes
gdscript
undefined
问题:角色在斜坡上滑动
gdscript
undefined

Solution: Increase friction

解决方案:增加摩擦力

const FRICTION := 1200.0

**Issue**: Character stutters on moving platforms
```gdscript
const FRICTION := 1200.0

**问题**:角色在移动平台上卡顿
```gdscript

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
```gdscript
func _physics_process(delta: float) -> void: move_and_slide()
# 吸附到平台速度
if is_on_floor():
    var floor_velocity := get_platform_velocity()
    velocity += floor_velocity

**问题**:二段跳漏洞
```gdscript

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
undefined
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
undefined

Reference

参考资料

Related

相关内容

  • Master Skill: godot-master
  • 大师技能:godot-master