
Godot 4 CharacterBody2D 角色移动:输入、碰撞与八方向控制实战

刚接触 Godot 4 时,很多人会直接修改 Sprite2D.position 来移动玩家。画面确实动了,但角色遇到墙体时仍会穿过去,因为精灵只负责显示,不负责物理运动。
这篇文章从一个最小的俯视角角色场景出发,使用 CharacterBody2D、CollisionShape2D 和 Input.get_vector(),完成方向输入、等速斜向移动以及滑动碰撞。最终代码可以直接作为 RPG、射击或探索类 2D 游戏的移动基础。
一、先分清三种物理主体
Godot 4 中常见的二维物理主体有三种:
CharacterBody2D:速度由玩家代码或 AI 决定,适合角色。StaticBody2D:提供固定阻挡,适合墙壁、地面和不可移动障碍。RigidBody2D:主要由重力、碰撞与外力推动,适合箱子等物理对象。
关键不在于对象看起来会不会动,而在于"谁决定它如何运动"。玩家角色需要稳定、可预测的输入响应,因此这里选择 CharacterBody2D。
二、搭建玩家场景
新建一个二维场景,将根节点设为 CharacterBody2D,命名为 Player。添加两个子节点:
text
Player (CharacterBody2D)
├── AnimatedSprite2D
└── CollisionShape2D

AnimatedSprite2D 负责画面,CollisionShape2D 负责物理轮廓。两者应当分离:头发、武器和披风等装饰像素通常不该参与阻挡,否则玩家会觉得"明明没碰到墙却被卡住"。
在 CollisionShape2D 的 Shape 属性中创建矩形、圆形或胶囊形状,优先覆盖角色的主体区域。俯视角角色没有明确的地面方向,可以把 CharacterBody2D 的 Motion Mode 设为 Floating。
三、配置输入动作
打开 Project → Project Settings → Input Map,建立四个动作:
| 动作名 | 推荐按键 |
|---|---|
move_left |
A、左方向键 |
move_right |
D、右方向键 |
move_up |
W、上方向键 |
move_down |
S、下方向键 |
代码只依赖动作名,不直接依赖某个键。以后接入手柄或允许玩家改键时,不必重写移动逻辑。
四、编写八方向移动代码
给 Player 创建并附加 player_controller.gd:
gdscript
extends CharacterBody2D
@export var speed: float = 240.0
func _physics_process(_delta: float) -> void:
var input_direction := Input.get_vector(
"move_left",
"move_right",
"move_up",
"move_down"
)
velocity = input_direction * speed
move_and_slide()
这段代码只有三个关键步骤:
Input.get_vector()把四个动作合成为一个Vector2。- 方向向量乘以
speed,得到角色速度。 move_and_slide()提交当前物理帧的速度并处理滑动碰撞。
不要再额外写 position += velocity * delta。CharacterBody2D 的运动应交给 move_and_slide(),否则可能避开预期的碰撞处理。
五、为什么斜着走不会更快
如果分别判断四个按键,再把 X、Y 速度都设为 speed,角色斜向移动时的合速度会大于水平速度。Input.get_vector() 会把最终向量长度限制在 1 以内,因此右下方向大约是 (0.707, 0.707),乘以速度后仍保持统一的最大移动速度。
这也是它比手工堆叠四个 if 更适合八方向控制的原因。

六、添加可碰撞的障碍
在测试场景中添加 StaticBody2D,并给它添加:
text
Wall (StaticBody2D)
├── Sprite2D
└── CollisionShape2D
运行后让玩家斜着撞向墙角。正确结果是:
- 角色不会穿过墙体;
- 仍有切向速度时,角色会沿墙滑动;
- 松开输入后,角色立即停止。
如果角色直接穿墙,依次检查:
- 玩家和墙体是否都有可用的
CollisionShape2D; - Shape 是否为空;
- 玩家与墙体的 Collision Layer 和 Mask 是否互相匹配;
- 是否错误地手动修改了
position; move_and_slide()是否在_physics_process()中执行。
七、加入平滑加速与减速
即时停止适合强调精准操作的游戏。如果希望移动更柔和,可以让速度逐步接近目标值:
gdscript
extends CharacterBody2D
@export var max_speed: float = 240.0
@export var acceleration: float = 1200.0
@export var deceleration: float = 1600.0
func _physics_process(delta: float) -> void:
var input_direction := Input.get_vector(
"move_left",
"move_right",
"move_up",
"move_down"
)
var target_velocity := input_direction * max_speed
var rate := acceleration if not input_direction.is_zero_approx() else deceleration
velocity.x = move_toward(velocity.x, target_velocity.x, rate * delta)
velocity.y = move_toward(velocity.y, target_velocity.y, rate * delta)
move_and_slide()
move_toward() 按固定变化率逼近目标速度,delta 用来抵消帧率差异。加速度和减速度分开后,可以做出"起步柔和、松手迅速"的手感。
八、常见错误
错误一:用 Sprite2D 当角色根节点
它可以显示和移动,但不会自动获得角色碰撞运动能力。需要把 CharacterBody2D 作为物理主体。
错误二:碰撞轮廓完全包住装饰像素
过大的形状容易让角色在狭窄通道和墙角处被卡住。物理轮廓应服务于玩法手感,而不是逐像素描边。
错误三:一个物理帧多次调用 move_and_slide()
先计算完整速度,再调用一次即可。把不同方向拆成多次移动会让碰撞结果难以预测。
错误四:把 velocity 再乘一次 delta
velocity 的单位是每秒像素数,move_and_slide() 会按物理步处理。只有加速度的变化量需要乘 delta。
九、知识主线与参考
本文的节点选型、碰撞轮廓、Floating 运动模式和物理移动方法,来自《大鹏 Godot 4 游戏开发入门》第 5 章中"配置可移动物理角色"与"编写物理角色移动代码"两节。文章把这些知识整理成一个可独立复现的俯视角控制器,没有把同题搜索结果当作技术事实。
十、完整验收清单
- 单独按 W、A、S、D 时方向正确。
- 同时按两个方向时速度没有变快。
- 松开输入后按预期立即停止或平滑减速。
- 碰到墙体不会穿透。
- 斜撞墙体时可以沿墙滑动。
- 调整窗口刷新率后,移动速度基本一致。
总结
完成这些检查后,一个可复用的 Godot 4 二维角色移动底座就建立好了。下一步可以把 velocity 交给 AnimatedSprite2D,让站立和移动动画随真实运动状态自动切换。