為什麼碰撞圖層很重要

碰撞圖層是 Godot 4 中所有物理互動的基礎 — 移動、命中判定、射線投射、Area 觸發等等。理解 LayerMask 之間的差異至關重要,而誤解它正是新手與資深開發者都最常遇到的絆腳石之一。

Layer 與 Mask — 思維模型

Godot 中每個物理物件都有兩個位元遮罩屬性:

基本規則

當 A 的 Mask 包含 B 的 Layer B 的 Mask 包含 A 的 Layer 時,A 會與 B 碰撞。只要有一方「看見」另一方,碰撞就會發生。

在專案設定中為圖層命名

在撰寫任何程式碼之前,先為你的圖層命名。這會讓檢視器(Inspector)更容易使用,並在專案成長時避免混淆。

專案設定 > 圖層名稱 > 2D 物理(或 3D 物理):

圖層編號 名稱 用途
1Player玩家角色
2Enemy所有敵人物體
3Environment牆壁、地板、平台
4Projectile子彈、箭矢、法術
5Pickup道具、金幣、補血包
6Trigger感測器、生成區、檢查點

在程式碼中設定圖層(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 說明
Player12, 3, 5, 6偵測敵人、環境、道具、觸發器
Enemy21, 3偵測玩家與環境
Environment3被動 — 被其他物件偵測
Projectile42, 3命中敵人與牆壁
Pickup5被動 — 由玩家偵測
Trigger61僅偵測玩家

俯視 / Roguelike

物件 Layer Mask 說明
Player12, 3, 5, 6偵測敵人、牆壁、NPC、感測器
Enemy21, 3偵測玩家與牆壁
Wall3被動
Bullet41, 2, 3命中玩家、敵人與牆壁
NPC53僅與牆壁碰撞
Sensor61偵測玩家的進入

Area2D / Area3D 圖層

Area 使用相同的 Layer/Mask 系統。此外,它們還有兩個開關屬性:

訊號: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)。這就是如何純粹透過圖層設定來防止友軍傷害的方法 — 完全不需要程式碼。

除錯技巧

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 — $15
相關工具: setup_collision set_physics_layers get_physics_layers get_collision_info add_raycast