ทำไม Collision Layers จึงสำคัญ
Collision layers คือรากฐานของทุกการโต้ตอบด้าน physics ใน Godot 4 — การเคลื่อนที่, การตรวจจับการชน, raycasting, ทริกเกอร์ของ Area และอื่น ๆ การเข้าใจความแตกต่างระหว่าง Layer และ Mask เป็นสิ่งสำคัญอย่างยิ่ง และการเข้าใจผิดในเรื่องนี้เป็นหนึ่งในจุดที่สร้างความปวดหัวมากที่สุดทั้งสำหรับมือใหม่และนักพัฒนาที่มีประสบการณ์
Layer กับ Mask — แบบจำลองความคิด
ทุกวัตถุ physics ใน Godot มีคุณสมบัติ bitmask สองอย่าง:
- Layer = "ฉันอยู่บนเลเยอร์เหล่านี้" (สิ่งที่ฉันเป็น)
- Mask = "ฉันสแกนเลเยอร์เหล่านี้" (สิ่งที่ฉันมองเห็น / ชนได้)
กฎสำคัญ
A ชนกับ B เมื่อ Mask ของ A มี Layer ของ B หรือ Mask ของ B มี Layer ของ A เพียงฝ่ายเดียวที่ "มองเห็น" อีกฝ่ายก็เพียงพอที่จะทำให้เกิดการชนได้
การตั้งชื่อเลเยอร์ใน Project Settings
ก่อนที่จะเขียนโค้ดใด ๆ ให้ตั้งชื่อเลเยอร์ของคุณก่อน สิ่งนี้ทำให้ Inspector ใช้งานง่ายขึ้นมาก และป้องกันความสับสนเมื่อโปรเจกต์ของคุณเติบโตขึ้น
Project Settings > Layer Names > 2D Physics (หรือ 3D Physics):
| Layer # | ชื่อ | วัตถุประสงค์ |
|---|---|---|
| 1 | Player | ตัวละครผู้เล่น |
| 2 | Enemy | ตัวศัตรูทั้งหมด |
| 3 | Environment | กำแพง, พื้น, แพลตฟอร์ม |
| 4 | Projectile | กระสุน, ลูกธนู, คาถา |
| 5 | Pickup | ไอเทม, เหรียญ, ชุดฟื้นฟูพลัง |
| 6 | Trigger | เซนเซอร์, โซนเกิด, จุดเช็กพอยต์ |
การตั้งค่าเลเยอร์ในโค้ด (GDScript)
Godot 4 มีสองวิธี — API แบบอิงค่า (แนะนำ) และการกำหนด bitmask โดยตรง:
# 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)
จุดพลาดของ Bitmask
หมายเลขเลเยอร์ใน set_collision_layer_value() เริ่มนับที่ 1 แต่ bitmask ที่อยู่เบื้องหลังเริ่มนับที่ 0 Layer 1 = bit 0 = ค่า 1, Layer 2 = bit 1 = ค่า 2, Layer 3 = bit 2 = ค่า 4 เมื่อไม่แน่ใจ ให้ใช้ API แบบอิงค่าเพื่อหลีกเลี่ยงข้อผิดพลาด
รูปแบบเลเยอร์ที่พบบ่อย
Platformer / Side-scroller
| วัตถุ | Layer | Mask | คำอธิบาย |
|---|---|---|---|
| Player | 1 | 2, 3, 5, 6 | ตรวจจับศัตรู, สภาพแวดล้อม, ไอเทม, ทริกเกอร์ |
| Enemy | 2 | 1, 3 | ตรวจจับผู้เล่นและสภาพแวดล้อม |
| Environment | 3 | — | เชิงรับ — ถูกตรวจจับโดยวัตถุอื่น |
| Projectile | 4 | 2, 3 | ชนศัตรูและกำแพง |
| Pickup | 5 | — | เชิงรับ — ผู้เล่นเป็นฝ่ายตรวจจับ |
| Trigger | 6 | 1 | ตรวจจับเฉพาะผู้เล่น |
Top-down / 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— หากเป็นtrueArea นี้จะตรวจจับวัตถุอื่นที่เข้ามาอย่างจริงจัง -
monitorable— หากเป็นtrueArea อื่นจะสามารถตรวจจับ Area นี้ได้
สัญญาณ: area_entered กับ body_entered
body_entered จะทำงานเมื่อ PhysicsBody (CharacterBody, RigidBody, StaticBody) เข้าสู่ Area area_entered จะทำงานเมื่อArea อื่นเข้ามา ตรวจสอบให้แน่ใจว่าคุณเชื่อมต่อสัญญาณที่ถูกต้องสำหรับกรณีการใช้งานของคุณ
# 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()
Collision Mask ของ 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
เคล็ดลับ: การป้องกัน Friendly Fire
สังเกตว่า Player Bullet มาสก์เลเยอร์ 2 (Enemy) แต่ไม่มาสก์เลเยอร์ 1 (Player) และ Enemy Bullet มาสก์เลเยอร์ 1 (Player) แต่ไม่มาสก์เลเยอร์ 2 (Enemy) นี่คือวิธีป้องกัน friendly fire ด้วยการตั้งค่าเลเยอร์ล้วน ๆ — ไม่ต้องเขียนโค้ดเลย
เคล็ดลับการดีบัก
- Visible Collision Shapes: ในเอดิเตอร์ ไปที่ Debug > Visible Collision Shapes เพื่อแสดงคอลลิชันเชปทั้งหมดขณะรันไทม์ สิ่งนี้จะเผยให้เห็นคอลไลเดอร์ที่ขาดหายไปหรือวางผิดตำแหน่งได้ทันที
- ตรวจสอบใน Inspector: เลือกโหนดแล้วขยาย Collision > Layer และ Collision > Mask ใน Inspector วางเมาส์เหนือแต่ละบิตเพื่อดูชื่อของมัน (หากคุณตั้งชื่อไว้ใน Project Settings)
-
พิมพ์ค่าขณะรันไทม์:
print("Layer: ", collision_layer, " Mask: ", collision_mask)เพื่อตรวจสอบค่า bitmask ระหว่างเล่นเกม
การเปลี่ยนแปลงในการย้ายจาก 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 ว่างเปล่า (เป็นศูนย์ทั้งหมด) วัตถุมีอยู่ในโลก physics แต่ไม่ตรวจจับอะไรเลย วัตถุอื่นที่มี mask ตรงกันจะยังตรวจจับมันได้ แต่ move_and_slide() บนวัตถุนี้จะทะลุผ่านทุกอย่าง
2. สับสนระหว่าง Layer กับ Mask
ตั้งค่า Layer ของ Player เป็น 2 (Enemy) แทนที่จะเป็น Mask ของมัน ตอนนี้ในมุมมองของ physics engine ผู้เล่นกลายเป็นศัตรูไปแล้ว จงจำไว้เสมอ: Layer = สิ่งที่ฉันเป็น, Mask = สิ่งที่ฉันสแกน
3. ใช้ค่า bitmask แทนหมายเลขเลเยอร์
เขียน set_collision_layer_value(4, true) โดยคิดว่ามันตั้งค่า bitmask เป็น 4 (เลเยอร์ 1+2) แต่ในความเป็นจริง มันเปิดใช้งานเลเยอร์ 4 API แบบอิงค่ารับหมายเลขเลเยอร์ ไม่ใช่ค่าบิต
4. ตรวจจับทางเดียวทั้งที่คาดหวังแบบสองทาง
วัตถุ A มาสก์เลเยอร์ของ B แต่ B ไม่ได้มาสก์เลเยอร์ของ A move_and_slide() บน A จะชนกับ B แต่ move_and_slide() บน B จะทะลุผ่าน A เพื่อให้โหนด CharacterBody สองตัวบล็อกกันและกัน ทั้งสองจะต้องมีเลเยอร์ของอีกฝ่ายอยู่ใน mask ของตน
ทำการตั้งค่า Physics อัตโนมัติด้วย Godot MCP Pro
เลิกสลับเช็กบ็อกซ์เลเยอร์ด้วยมือได้แล้ว ให้ AI กำหนดค่าการตั้งค่า collision ทั้งหมดของคุณภายในไม่กี่วินาที — รวมถึงการตั้งชื่อเลเยอร์, การกำหนด mask และการเพิ่ม raycast
รับ Godot MCP Pro — $15setup_collision
set_physics_layers
get_physics_layers
get_collision_info
add_raycast