1. 引言
Godot 4 对角色移动引入了重大变化。旧的 KinematicBody2D 和 KinematicBody3D 节点被重命名为 CharacterBody2D 和 CharacterBody3D,而 move_and_slide() API 也被彻底重新设计。无论你是从 Godot 3 迁移还是从零开始,本指南都涵盖了你需要了解的一切。
CharacterBody2D 和 CharacterBody3D 是专为通过代码控制的角色而设计的物理体。与 RigidBody 不同,它们不会自动响应力——你完全通过脚本来控制它们的移动。这使它们成为玩家角色、NPC,以及任何需要精确、确定性移动的对象的理想选择。
2. 相较于 Godot 3 的变化
以下是 Godot 3 到 Godot 4 中角色移动方面最重要的变化:
-
KinematicBody2D现在是CharacterBody2D -
KinematicBody3D现在是CharacterBody3D -
velocity现在是内置属性——你不再需要把它传给move_and_slide() -
move_and_slide()不接受任何参数——它直接从velocity属性读取 -
move_and_slide_with_snap()已被移除——请改用floor_snap_length属性 -
is_on_floor()、is_on_wall()、is_on_ceiling()的用法保持不变 -
get_gravity()在 Godot 4.4 中新增,可将项目的默认重力作为向量读取——非常方便
在 Godot 3 中,move_and_slide() 返回计算后的速度。在 Godot 4 中,它返回一个 bool(是否发生了碰撞),而速度会就地更新到 velocity 属性上。
3. 基础 2D 平台游戏控制器
这是标准的横向卷轴平台游戏控制器。角色可以左右移动,并在地面上时跳跃。这也是你创建新的 CharacterBody2D 脚本时 Godot 生成的模板。
extends CharacterBody2D
const SPEED = 300.0
const JUMP_VELOCITY = -400.0
func _physics_process(delta: float) -> void:
# Apply gravity when not on floor
if not is_on_floor():
velocity += get_gravity() * delta
# Jump when on floor and jump pressed
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
# Horizontal movement
var direction := Input.get_axis("ui_left", "ui_right")
if direction:
velocity.x = direction * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
move_and_slide()
逐行解析
-
extends CharacterBody2D— 该脚本继承自 CharacterBody2D,从而可以访问velocity、move_and_slide()、is_on_floor()等。 -
SPEED = 300.0— 水平移动速度,单位为像素/秒。 -
JUMP_VELOCITY = -400.0— 为负值,因为 2D 中 Y 轴指向下方。负值会使角色向上移动。 -
get_gravity() * delta— 每帧施加项目重力。get_gravity()返回一个指向下方的Vector2(例如Vector2(0, 980))。乘以delta可使其不受帧率影响。 -
Input.get_axis("ui_left", "ui_right")— 根据按下的方向键返回 -1.0 到 1.0 的浮点数。支持模拟输入。 -
move_toward(velocity.x, 0, SPEED)— 在没有输入时平滑地减速到零。第三个参数是每次调用的最大变化量。 -
move_and_slide()— 使用当前velocity移动物理体,处理碰撞,沿表面滑动,并自动更新velocity。
你的 CharacterBody2D 至少需要一个带有指定形状(如 RectangleShape2D 或 CapsuleShape2D)的 CollisionShape2D 子节点。否则 move_and_slide() 将无法检测到任何碰撞。
4. 基础 2D 俯视角控制器
对于俯视角游戏(RPG、双摇杆射击等),你在没有重力的情况下向四个方向移动。代码比平台游戏更简单。
extends CharacterBody2D
const SPEED = 200.0
func _physics_process(delta: float) -> void:
var input_direction := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
velocity = input_direction * SPEED
move_and_slide()
工作原理
-
Input.get_vector()从四个输入动作返回一个归一化的Vector2。它能正确处理对角线移动——该向量的长度始终为 1.0 或 0.0,因此对角线移动不会比横竖方向移动更快。 -
由于这是俯视角,不施加重力。由于我们直接设置
velocity(没有惯性),在没有输入时角色会立即停止。
添加加速度与摩擦力
若要获得带加速和减速的更流畅手感:
extends CharacterBody2D
const SPEED = 200.0
const ACCELERATION = 1200.0
const FRICTION = 1000.0
func _physics_process(delta: float) -> void:
var input_direction := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
if input_direction != Vector2.ZERO:
velocity = velocity.move_toward(input_direction * SPEED, ACCELERATION * delta)
else:
velocity = velocity.move_toward(Vector2.ZERO, FRICTION * delta)
move_and_slide()
5. 3D 第三人称控制器
3D 版本遵循相同的模式。主要区别在于使用 Vector3,并通过 transform.basis 将 2D 输入转换为 3D 世界空间中的移动。
extends CharacterBody3D
const SPEED = 5.0
const JUMP_VELOCITY = 4.5
func _physics_process(delta: float) -> void:
# Apply gravity
if not is_on_floor():
velocity += get_gravity() * delta
# Jump
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
# Get input and convert to 3D direction
var input_dir := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
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()
与 2D 的主要区别
-
单位:在 3D 中,1 单位 = 1 米(惯例)。所以
SPEED = 5.0表示每秒 5 米——数值比 2D 的像素值小得多。 -
跳跃速度为正值:在 3D 中,Y 轴指向上方(与 2D 相反),所以
JUMP_VELOCITY = 4.5为正值。 -
方向变换:
transform.basis * Vector3(input_dir.x, 0, input_dir.y)将 2D 输入转换为相对于角色朝向的 3D 方向。输入的 Y 映射到 Z 轴(3D 中的前后)。 -
仅控制 X 和 Z:我们分别设置
velocity.x和velocity.z,将velocity.y留给重力和跳跃。
get_gravity() 方法在 Godot 4.4 中新增。对于更早的版本,2D 请使用 Vector2(0, ProjectSettings.get_setting("physics/2d/default_gravity")),3D 请使用 Vector3(0, -ProjectSettings.get_setting("physics/3d/default_gravity"), 0)。
6. 关键属性参考
CharacterBody2D 和 CharacterBody3D 都共享这些重要属性。你可以在检查器中或通过代码调整它们。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
velocity |
Vector2 / Vector3 | Vector2.ZERO |
角色的速度。在调用 move_and_slide() 之前设置它。调用之后会用计算得到的速度更新它。
|
floor_snap_length |
float | 1.0 |
替代 move_and_slide_with_snap()。将角色贴附到地面的距离。设为 0 可禁用。对坡度和台阶很有用。
|
up_direction |
Vector2 / Vector3 | Vector2.UP |
定义哪个方向是“上”。这决定了什么算作地板、墙壁或天花板。2D 中默认为 (0, -1),3D 中默认为 (0, 1, 0)。
|
floor_stop_on_slope |
bool | true |
为 true 时,角色静止时不会沿坡度下滑。对平台游戏至关重要。 |
floor_max_angle |
float | 0.785 (45°) |
可行走的最大坡度角(以弧度表示)。比此更陡的表面会被视为墙壁。使用 deg_to_rad(60) 可设为 60 度。
|
max_slides |
int | 6 |
每次 move_and_slide() 调用的最大碰撞迭代次数。值越高越精确,但速度越慢。
|
wall_min_slide_angle |
float | 0.262 (15°) |
墙面滑动的最小角度。防止角色卡在几乎平行的墙壁上。 |
platform_on_leave |
PlatformOnLeave | ADD_VELOCITY |
离开移动平台时的行为。ADD_VELOCITY 保留动量,ADD_UPWARD_VELOCITY 只添加向上的分量,DO_NOTHING 忽略平台的速度。
|
slide_on_ceiling |
bool | true |
为 true 时,允许在天花板上滑动。为 false 时,撞到天花板时停止水平速度。 |
7. move_and_slide() 之后的碰撞检测
调用 move_and_slide() 之后,你可以检查发生的碰撞。这对于响应特定的碰撞体类型、着地时播放音效或实现蹬墙跳机制很有用。
func _physics_process(delta: float) -> void:
# ... set velocity here ...
move_and_slide()
# Check all collisions from this frame
for i in get_slide_collision_count():
var collision := get_slide_collision(i)
var collider := collision.get_collider()
print("Collided with: ", collider.name)
print("Normal: ", collision.get_normal())
print("Position: ", collision.get_position())
# Practical example: bounce off enemies
for i in get_slide_collision_count():
var collision := get_slide_collision(i)
if collision.get_collider().is_in_group("enemies"):
velocity = collision.get_normal() * 300
break
实用的 KinematicCollision 方法
get_collider()— 被撞击的对象。get_normal()— 碰撞表面的法线。指向远离该表面的方向。get_position()— 世界空间中发生碰撞的点。get_travel()— 物理体在碰撞之前移动的距离。get_remainder()— 未被应用的剩余运动。get_collider_velocity()— 碰撞体的速度(对移动平台很有用)。
8. 迁移速查表
将 Godot 3 代码转换为 Godot 4 的快速参考:
| Godot 3 | Godot 4 |
|---|---|
KinematicBody2D |
CharacterBody2D |
KinematicBody3D |
CharacterBody3D |
velocity = move_and_slide(velocity, Vector2.UP) |
velocity = ... |
move_and_slide_with_snap(vel, snap, up) |
floor_snap_length = 4.0 |
var vel = move_and_slide(...)返回速度 |
move_and_slide()velocity 属性就地更新 |
var gravity = ProjectSettings.get("physics/2d/default_gravity") |
get_gravity() (4.4+) |
move_and_slide(..., up_direction, ...) |
up_direction = Vector2.UP (作为属性设置) |
move_and_slide(..., stop_on_slope, ...) |
floor_stop_on_slope = true (作为属性设置) |
在 Godot 4 中打开 Godot 3 项目时,引擎会提示转换你的项目。它会重命名节点并尝试更新脚本,但并不总能正确处理 move_and_slide() 的调用。你很可能需要手动修复移动代码。
9. 常见错误
错误 1:向 move_and_slide() 传递参数
# ERROR: move_and_slide() takes no arguments in Godot 4
velocity = move_and_slide(velocity, Vector2.UP)
# Set velocity, then call move_and_slide() with no arguments
velocity.x = direction * SPEED
move_and_slide()
错误 2:在 move_and_slide() 之前忘记设置 velocity
func _physics_process(delta: float) -> void:
move_and_slide() # velocity is Vector2.ZERO — nothing happens
错误 3:使用 _process() 而非 _physics_process()
move_and_slide() 必须在 _physics_process() 中调用,它以固定速率运行(默认 60 Hz)。使用 _process() 会把物理与渲染帧率绑定,导致行为不一致。
错误 4:未设置 CollisionShape
没有 CollisionShape 子节点的 CharacterBody2D/CharacterBody3D 会穿过一切。如果缺少碰撞形状,Godot 会在编辑器中显示一个警告图标。
错误 5:每帧用重力覆盖 velocity.y
# This overwrites any jump velocity!
velocity.y = gravity * delta # should be +=, not =
# Gravity accumulates over time
velocity += get_gravity() * delta
错误 6:在地面上时仍施加重力
始终将重力包裹在 if not is_on_floor() 中。没有这个检查,重力会在地面上持续累积。当角色从边缘走下去时,它会以极快的速度骤降,而不是自然下落。