Godot 4 GDScript 教程 / 角色控制器(CharacterBody)
角色控制器(CharacterBody)
概述
CharacterBody 是 Godot 4 中专门用于可控角色的物理节点,替代了 Godot 3 中的 KinematicBody。它提供了 move_and_slide() 新 API,内置地板检测、斜坡与阶梯处理,是实现玩家角色、NPC 移动的核心节点。
| 节点 | 用途 | 说明 |
|---|---|---|
CharacterBody3D | 3D 角色 | 第/第三人称控制器 |
CharacterBody2D | 2D 角色 | 平台跳跃、俯视角移动 |
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 3 | Godot 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 + 土狼时间 + 跳跃缓冲 |
| 俯视角 RPG | CharacterBody3D + MOTION_MODE_FLOATING |
| 第一人称射击 | CharacterBody3D + 头部 Bob 效果 |
| 格斗游戏 | CharacterBody3D + 自定义加速度系统 |
⚠️ 常见陷阱
is_on_floor()必须在move_and_slide()之后调用- Godot 4 的
move_and_slide()不接收参数,必须先设置velocity - 不要在
_process()中调用物理操作,使用_physics_process() floor_snap_length设为 0 会导致角色在坡道上弹跳motion_mode为FLOATING时,地板检测失效