强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Godot 4 GDScript 教程 / 角色控制器(CharacterBody)

角色控制器(CharacterBody)

概述

CharacterBody 是 Godot 4 中专门用于可控角色的物理节点,替代了 Godot 3 中的 KinematicBody。它提供了 move_and_slide() 新 API,内置地板检测、斜坡与阶梯处理,是实现玩家角色、NPC 移动的核心节点。

节点用途说明
CharacterBody3D3D 角色第/第三人称控制器
CharacterBody2D2D 角色平台跳跃、俯视角移动

CharacterBody 核心属性

extends CharacterBody3D

# 内置属性(无需声明)
# velocity: Vector3  — 当前速度向量
# up_direction: Vector3 — 上方向,默认 Vector3.UP
# floor_max_angle: float — 最大坡度角(弧度),默认 0.785398(约 45°)
# floor_stop_on_slope: bool — 坡道上是否停止滑动
# max_slides: int — 最大滑动次数,默认 6
# wall_min_slide_angle: float — 最小墙壁滑动角
# motion_mode: MotionMode — MOTION_MODE_GROUNDED 或 MOTION_MODE_FLOATING

move_and_slide() 新 API

Godot 4 的 move_and_slide() 与 Godot 3 有重大变化:

对比项Godot 3Godot 4
调用方式move_and_slide(velocity, up)先设置 velocity,再调用 move_and_slide()
返回值新的速度向量bool(是否发生碰撞)
碰撞信息get_slide_collision()get_slide_collision()(不变)
地板检测is_on_floor()is_on_floor()(不变)

基本移动示例

extends CharacterBody3D

const SPEED = 5.0
const JUMP_VELOCITY = 4.5

# 获取重力(从项目设置中读取)
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")

func _physics_process(delta):
    # 1. 施加重力
    if not is_on_floor():
        velocity.y -= gravity * delta

    # 2. 跳跃
    if Input.is_action_just_pressed("ui_accept") and is_on_floor():
        velocity.y = JUMP_VELOCITY

    # 3. 获取输入方向
    var input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_back")
    var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()

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

    # 5. 执行移动(自动处理碰撞、斜坡、阶梯)
    move_and_slide()

⚠️ 注意:Godot 4 中 move_and_slide() 不再接收参数,必须先赋值 velocity 属性再调用。


地板检测

CharacterBody 内置了精确的地板检测:

func _physics_process(delta):
    move_and_slide()

    # 移动后检测状态
    if is_on_floor():
        print("角色在地面上")
        # 可以重置跳跃次数等
    elif is_on_wall():
        print("角色碰到了墙壁")
        # 可实现蹬墙跳
    elif is_on_ceiling():
        print("角色碰到了天花板")

多段跳实现

extends CharacterBody3D

const JUMP_VELOCITY = 4.5
const MAX_JUMPS = 3
var jump_count = 0

var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")

func _physics_process(delta):
    if is_on_floor():
        jump_count = 0

    if Input.is_action_just_pressed("jump") and jump_count < MAX_JUMPS:
        velocity.y = JUMP_VELOCITY
        jump_count += 1

    if not is_on_floor():
        velocity.y -= gravity * delta

    var input_dir = Input.get_vector("left", "right", "forward", "back")
    velocity.x = input_dir.x * 5.0
    velocity.z = input_dir.y * 5.0

    move_and_slide()

💡 提示is_on_floor() 必须在 move_and_slide() 之后调用,因为地板状态在移动后才更新。


斜坡处理

extends CharacterBody3D

func _ready():
    # 最大坡度角:45 度(默认)
    floor_max_angle = deg_to_rad(45)
    # 坡道上停止滑动
    floor_stop_on_slope = true
    # 坡道上的加速度(防滑)
    floor_snap_length = 0.5
属性说明默认值
floor_max_angle可行走的最大坡度角0.785398(45°)
floor_stop_on_slope坡道上是否自动停止true
floor_snap_length地板吸附距离0.1
floor_constant_speed坡道上保持恒定速度false
platform_on_leave离开移动平台时的行为PLATFORM_ON_LEAVE_ADD_VELOCITY

⚠️ 注意:如果角色在斜坡上滑动,检查 floor_max_angle 是否过小,或 floor_stop_on_slope 是否为 false


阶梯处理

Godot 4 内置了阶梯自动抬升:

func _ready():
    # Godot 4.3+ 支持 step_height 属性
    # 设置阶梯最大高度(米),角色会自动抬升跨越
    pass

在 Godot 4.3+ 中,CharacterBody3D 新增 step_height 属性(float),设置后自动处理阶梯。

💡 提示:阶梯高度应略大于场景中实际阶梯高度,建议设置为 0.3~0.5 米。


第三人称控制器实现

extends CharacterBody3D

@export var speed: float = 5.0
@export var jump_velocity: float = 4.5
@export var rotation_speed: float = 10.0
@export var camera_path: NodePath

var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
@onready var camera: Camera3D = get_node(camera_path)
@onready var model: Node3D = $Model  # 角色模型

func _physics_process(delta):
    # 重力
    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 input_dir = Input.get_vector("left", "right", "forward", "back")
    # 相对于相机的移动方向
    var cam_basis = camera.global_transform.basis
    var direction = (cam_basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()

    if direction:
        velocity.x = direction.x * speed
        velocity.z = direction.z * speed
        # 模型朝向移动方向(带平滑旋转)
        var target_angle = atan2(direction.x, direction.z)
        model.rotation.y = lerp_angle(model.rotation.y, target_angle, rotation_speed * delta)
    else:
        velocity.x = move_toward(velocity.x, 0, speed)
        velocity.z = move_toward(velocity.z, 0, speed)

    move_and_slide()

2D 平台跳跃控制器

extends CharacterBody2D

const SPEED = 300.0
const JUMP_VELOCITY = -400.0
const WALL_JUMP_VELOCITY = Vector2(200, -400)
const COYOTE_TIME = 0.15  # 土狼时间
const JUMP_BUFFER_TIME = 0.1

var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
var coyote_timer: float = 0.0
var jump_buffer_timer: float = 0.0

func _physics_process(delta):
    # 重力
    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

    # 跳跃(含土狼时间和跳跃缓冲)
    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("left", "right")
    velocity.x = direction * SPEED

    # 蹬墙跳
    if is_on_wall() and Input.is_action_just_pressed("jump"):
        var wall_normal = get_wall_normal()
        velocity = Vector2(-wall_normal.x * WALL_JUMP_VELOCITY.x, WALL_JUMP_VELOCITY.y)

    move_and_slide()

💡 提示土狼时间(Coyote Time) 让玩家离开平台边缘后仍有一小段时间可以跳跃,大幅提升手感。跳跃缓冲(Jump Buffer) 让玩家在落地前提前按跳跃键也能触发跳跃。


角色动画状态机集成

extends CharacterBody3D

@onready var anim_tree: AnimationTree = $AnimationTree
@onready var state_machine = anim_tree["parameters/playback"]

const SPEED = 5.0
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")

func _physics_process(delta):
    if not is_on_floor():
        velocity.y -= gravity * delta

    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = 4.5

    var input_dir = Input.get_vector("left", "right", "forward", "back")
    var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()

    if direction:
        velocity.x = direction.x * SPEED
        velocity.z = direction.z * SPEED
    else:
        velocity.x = move_toward(velocity.x, 0, SPEED)
        velocity.z = move_toward(velocity.z, 0, SPEED)

    move_and_slide()

    # 动画状态切换
    var h_velocity = Vector2(velocity.x, velocity.z)
    if not is_on_floor():
        if velocity.y > 0:
            state_machine.travel("Jump")
        else:
            state_machine.travel("Fall")
    elif h_velocity.length() > 0.1:
        state_machine.travel("Run")
    else:
        state_machine.travel("Idle")

自定义移动系统

有时你需要更精细的移动控制,可以通过加速度和摩擦力实现:

extends CharacterBody3D

const SPEED = 5.0
const ACCELERATION = 10.0
const FRICTION = 8.0
const AIR_CONTROL = 0.3  # 空中控制系数

var input_velocity := Vector3.ZERO
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")

func _physics_process(delta):
    # 重力
    if not is_on_floor():
        velocity.y -= gravity * delta

    # 输入
    var input_dir = Input.get_vector("left", "right", "forward", "back")
    input_velocity = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized() * SPEED

    # 加速/减速
    var accel = ACCELERATION if is_on_floor() else ACCELERATION * AIR_CONTROL
    if input_dir.length() > 0:
        velocity.x = lerp(velocity.x, input_velocity.x, accel * delta)
        velocity.z = lerp(velocity.z, input_velocity.z, accel * delta)
    else:
        velocity.x = lerp(velocity.x, 0.0, FRICTION * delta)
        velocity.z = lerp(velocity.z, 0.0, FRICTION * delta)

    move_and_slide()

游戏开发场景

场景推荐方案
3D 第三人称动作CharacterBody3D + AnimationTree 状态机
2D 平台跳跃CharacterBody2D + 土狼时间 + 跳跃缓冲
俯视角 RPGCharacterBody3D + MOTION_MODE_FLOATING
第一人称射击CharacterBody3D + 头部 Bob 效果
格斗游戏CharacterBody3D + 自定义加速度系统

⚠️ 常见陷阱

  1. is_on_floor() 必须在 move_and_slide() 之后调用
  2. Godot 4 的 move_and_slide() 不接收参数,必须先设置 velocity
  3. 不要在 _process() 中调用物理操作,使用 _physics_process()
  4. floor_snap_length 设为 0 会导致角色在坡道上弹跳
  5. motion_modeFLOATING 时,地板检测失效

扩展阅读