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 都共用這些重要屬性。你可以在檢視器(Inspector)中或透過程式碼調整它們。
| 屬性 | 型別 | 預設值 | 說明 |
|---|---|---|---|
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(...)回傳 velocity |
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() 把重力包起來。少了這項檢查,重力會在著地時持續累積。當角色走出邊緣時,就會以極快的速度墜落,而非自然地下落。