為什麼碰撞圖層很重要
碰撞圖層是 Godot 4 中所有物理互動的基礎 — 移動、命中判定、射線投射、Area 觸發等等。理解 Layer 與 Mask 之間的差異至關重要,而誤解它正是新手與資深開發者都最常遇到的絆腳石之一。
Layer 與 Mask — 思維模型
Godot 中每個物理物件都有兩個位元遮罩屬性:
- Layer = 「我存在於這些圖層上」(我是什麼)
- Mask = 「我掃描這些圖層」(我能看見/命中什麼)
基本規則
當 A 的 Mask 包含 B 的 Layer 或 B 的 Mask 包含 A 的 Layer 時,A 會與 B 碰撞。只要有一方「看見」另一方,碰撞就會發生。
在專案設定中為圖層命名
在撰寫任何程式碼之前,先為你的圖層命名。這會讓檢視器(Inspector)更容易使用,並在專案成長時避免混淆。
專案設定 > 圖層名稱 > 2D 物理(或 3D 物理):
| 圖層編號 | 名稱 | 用途 |
|---|---|---|
| 1 | Player | 玩家角色 |
| 2 | Enemy | 所有敵人物體 |
| 3 | Environment | 牆壁、地板、平台 |
| 4 | Projectile | 子彈、箭矢、法術 |
| 5 | Pickup | 道具、金幣、補血包 |
| 6 | Trigger | 感測器、生成區、檢查點 |
在程式碼中設定圖層(GDScript)
Godot 4 提供兩種做法 — 以數值為基礎的 API(推薦)以及直接的位元遮罩指派:
# Godot 4 API — value-based (1-indexed, recommended)
set_collision_layer_value(1, true) # I am on layer 1
set_collision_mask_value(2, true) # I detect layer 2
# Or use bitmask directly
collision_layer = 1 # Layer 1 only (bit 0)
collision_mask = 6 # Layers 2 and 3 (bits 1 + 2 = 6)
# Read current state
var on_layer_1: bool = get_collision_layer_value(1)
var scans_layer_2: bool = get_collision_mask_value(2)
位元遮罩的陷阱
在 set_collision_layer_value() 中,圖層編號是從 1 開始的,但底層的位元遮罩是從 0 開始的。圖層 1 = 位元 0 = 值 1,圖層 2 = 位元 1 = 值 2,圖層 3 = 位元 2 = 值 4。有疑慮時,請使用以數值為基礎的 API 以避免錯誤。
常見的圖層配置
平台遊戲 / 橫向捲軸
| 物件 | Layer | Mask | 說明 |
|---|---|---|---|
| Player | 1 | 2, 3, 5, 6 | 偵測敵人、環境、道具、觸發器 |
| Enemy | 2 | 1, 3 | 偵測玩家與環境 |
| Environment | 3 | — | 被動 — 被其他物件偵測 |
| Projectile | 4 | 2, 3 | 命中敵人與牆壁 |
| Pickup | 5 | — | 被動 — 由玩家偵測 |
| Trigger | 6 | 1 | 僅偵測玩家 |
俯視 / Roguelike
| 物件 | Layer | Mask | 說明 |
|---|---|---|---|
| Player | 1 | 2, 3, 5, 6 | 偵測敵人、牆壁、NPC、感測器 |
| Enemy | 2 | 1, 3 | 偵測玩家與牆壁 |
| Wall | 3 | — | 被動 |
| Bullet | 4 | 1, 2, 3 | 命中玩家、敵人與牆壁 |
| NPC | 5 | 3 | 僅與牆壁碰撞 |
| Sensor | 6 | 1 | 偵測玩家的進入 |
Area2D / Area3D 圖層
Area 使用相同的 Layer/Mask 系統。此外,它們還有兩個開關屬性:
-
monitoring— 若為true,此 Area 會主動偵測進入它的其他物件。 -
monitorable— 若為true,其他 Area 便能偵測到此 Area。
訊號:area_entered 與 body_entered
當一個 PhysicsBody(CharacterBody、RigidBody、StaticBody)進入 Area 時,body_entered 會被觸發。當另一個 Area 進入時,area_entered 會被觸發。請務必為你的使用情境連接正確的訊號。
# Pickup Area2D — detect when the Player body enters
func _ready() -> void:
body_entered.connect(_on_body_entered)
func _on_body_entered(body: Node2D) -> void:
if body.is_in_group("player"):
collect()
queue_free()
RayCast 碰撞遮罩
RayCast 節點只有 Mask(沒有 Layer),因為它們是偵測器,而非物理物體。設定 Mask 以過濾射線可以命中的對象:
# RayCast that only detects enemies (layer 2)
$RayCast2D.collision_mask = 2 # Layer 2 = Enemy
# Or use the value-based API:
$RayCast2D.set_collision_mask_value(1, false) # Ignore Player
$RayCast2D.set_collision_mask_value(2, true) # Detect Enemy
$RayCast2D.set_collision_mask_value(3, false) # Ignore Environment
# Line-of-sight ray that ignores projectiles
$LineOfSight.collision_mask = 0
$LineOfSight.set_collision_mask_value(1, true) # Player
$LineOfSight.set_collision_mask_value(3, true) # Environment
實務範例:Player – Enemy – Projectile
以下是典型動作遊戲的完整圖層設定。註解說明了每個決定背後的考量:
# Player (CharacterBody2D)
# Layer: 1 (Player) — "I am the player"
# Mask: 2 (Enemy), 3 (Environment), 5 (Pickup)
# — I collide with enemies, walls, and can pick up items
# Enemy (CharacterBody2D)
# Layer: 2 (Enemy) — "I am an enemy"
# Mask: 1 (Player), 3 (Environment)
# — I collide with the player and walls
# Player Bullet (Area2D)
# Layer: 4 (Projectile) — "I am a projectile"
# Mask: 2 (Enemy), 3 (Environment)
# — I hit enemies and walls, but NOT the player who fired me
# Enemy Bullet (Area2D)
# Layer: 4 (Projectile) — "I am a projectile"
# Mask: 1 (Player), 3 (Environment)
# — I hit the player and walls, but NOT the enemy who fired me
提示:防止友軍傷害
請注意,Player Bullet 遮罩了圖層 2(Enemy)但不遮罩圖層 1(Player),而 Enemy Bullet 遮罩了圖層 1(Player)但不遮罩圖層 2(Enemy)。這就是如何純粹透過圖層設定來防止友軍傷害的方法 — 完全不需要程式碼。
除錯技巧
- 可見碰撞形狀:在編輯器中,前往 除錯 > 可見碰撞形狀,即可在執行時繪製所有碰撞形狀。這能立即揭露缺失或錯位的碰撞體。
- 檢視器檢查:選取一個節點,並在檢視器中展開 Collision > Layer 和 Collision > Mask。將滑鼠移到每個位元上即可看到它的名稱(若你已在專案設定中為它們命名)。
-
在執行時輸出:
print("Layer: ", collision_layer, " Mask: ", collision_mask),以在遊玩期間驗證位元遮罩的值。
Godot 3 → 4 遷移變更
| Godot 3 | Godot 4 |
|---|---|
set_collision_layer_bit(bit, value) |
set_collision_layer_value(layer, value) |
set_collision_mask_bit(bit, value) |
set_collision_mask_value(layer, value) |
bit 參數是從 0 開始
|
layer 參數是從 1 開始
|
| 提供 20 個圖層 | 提供 32 個圖層 |
遷移時的絆腳石
若你正在移植 Godot 3 專案,請記得 set_collision_layer_bit(0, true) 會變成 set_collision_layer_value(1, true)。索引會位移 +1。忽略這一點的話,你所有的圖層都會相差一位。
常見錯誤
1. 忘記設定 Mask
你的物件有一個 Layer,但 Mask 是空的(全部為零)。該物件存在於物理世界中,卻不偵測任何東西。其他具有相符遮罩的物件仍然能偵測到它,但此物件上的 move_and_slide() 會穿透一切。
2. 混淆 Layer 與 Mask
將玩家的 Layer 設為 2(Enemy),而不是設定它的 Mask。如此一來,就物理引擎而言,玩家就是一個敵人。永遠記住:Layer = 我是什麼,Mask = 我掃描什麼。
3. 使用位元遮罩值而非圖層編號
寫下 set_collision_layer_value(4, true),以為它會設定位元遮罩值 4(圖層 1+2)。實際上,它啟用的是圖層 4。以數值為基礎的 API 接受的是圖層編號,而非位元值。
4. 期望雙向卻只有單向偵測
物件 A 遮罩了 B 的 Layer,但 B 並未遮罩 A 的 Layer。A 上的 move_and_slide() 會與 B 碰撞,但 B 上的 move_and_slide() 會穿透 A。若要讓兩個 CharacterBody 節點互相阻擋,雙方都必須在自己的 Mask 中包含對方的 Layer。
使用 Godot MCP Pro 自動化物理設定
別再手動切換圖層核取方塊了。讓 AI 在幾秒內設定好你整個碰撞配置 — 包括為圖層命名、指派遮罩以及新增射線投射。
取得 Godot MCP Pro — $15setup_collision
set_physics_layers
get_physics_layers
get_collision_info
add_raycast