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

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

刚接触 Godot 4 时,很多人会直接修改Sprite2D.position来移动玩家。画面确实动了,但角色遇到墙体时仍会穿过去,因为精灵只负责显示,不负责物理运动。

这篇文章从一个最小的俯视角角色场景出发,使用CharacterBody2DCollisionShape2DInput.get_vector(),完成方向输入、等速斜向移动以及滑动碰撞。最终代码可以直接作为 RPG、射击或探索类 2D 游戏的移动基础。

一、先分清三种物理主体

Godot 4 中常见的二维物理主体有三种:

  • CharacterBody2D:速度由玩家代码或 AI 决定,适合角色。
  • StaticBody2D:提供固定阻挡,适合墙壁、地面和不可移动障碍。
  • RigidBody2D:主要由重力、碰撞与外力推动,适合箱子等物理对象。

关键不在于对象看起来会不会动,而在于“谁决定它如何运动”。玩家角色需要稳定、可预测的输入响应,因此这里选择CharacterBody2D

二、搭建玩家场景

新建一个二维场景,将根节点设为CharacterBody2D,命名为Player。添加两个子节点:

Player (CharacterBody2D) ├── AnimatedSprite2D └── CollisionShape2D

AnimatedSprite2D负责画面,CollisionShape2D负责物理轮廓。两者应当分离:头发、武器和披风等装饰像素通常不该参与阻挡,否则玩家会觉得“明明没碰到墙却被卡住”。

CollisionShape2DShape属性中创建矩形、圆形或胶囊形状,优先覆盖角色的主体区域。俯视角角色没有明确的地面方向,可以把CharacterBody2DMotion Mode设为Floating

三、配置输入动作

打开Project → Project Settings → Input Map,建立四个动作:

动作名推荐按键
move_leftA、左方向键
move_rightD、右方向键
move_upW、上方向键
move_downS、下方向键

代码只依赖动作名,不直接依赖某个键。以后接入手柄或允许玩家改键时,不必重写移动逻辑。

四、编写八方向移动代码

Player创建并附加player_controller.gd

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

这段代码只有三个关键步骤:

  1. Input.get_vector()把四个动作合成为一个Vector2
  2. 方向向量乘以speed,得到角色速度。
  3. move_and_slide()提交当前物理帧的速度并处理滑动碰撞。

不要再额外写position += velocity * deltaCharacterBody2D的运动应交给move_and_slide(),否则可能避开预期的碰撞处理。

五、为什么斜着走不会更快

如果分别判断四个按键,再把 X、Y 速度都设为speed,角色斜向移动时的合速度会大于水平速度。Input.get_vector()会把最终向量长度限制在 1 以内,因此右下方向大约是(0.707, 0.707),乘以速度后仍保持统一的最大移动速度。

这也是它比手工堆叠四个if更适合八方向控制的原因。

六、添加可碰撞的障碍

在测试场景中添加StaticBody2D,并给它添加:

Wall (StaticBody2D) ├── Sprite2D └── CollisionShape2D

运行后让玩家斜着撞向墙角。正确结果是:

  • 角色不会穿过墙体;
  • 仍有切向速度时,角色会沿墙滑动;
  • 松开输入后,角色立即停止。

如果角色直接穿墙,依次检查:

  1. 玩家和墙体是否都有可用的CollisionShape2D
  2. Shape 是否为空;
  3. 玩家与墙体的 Collision Layer 和 Mask 是否互相匹配;
  4. 是否错误地手动修改了position
  5. move_and_slide()是否在_physics_process()中执行。

七、加入平滑加速与减速

即时停止适合强调精准操作的游戏。如果希望移动更柔和,可以让速度逐步接近目标值:

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,让站立和移动动画随真实运动状态自动切换。