Tại sao collision layer lại quan trọng
Collision layer là nền tảng của mọi tương tác vật lý trong Godot 4 — di chuyển, phát hiện va chạm, raycasting, Area trigger và nhiều thứ khác. Hiểu được sự khác biệt giữa layer và mask là điều then chốt, và hiểu sai về nó là một trong những cạm bẫy phổ biến nhất đối với cả người mới lẫn lập trình viên có kinh nghiệm.
Layer vs mask — Mô hình tư duy
Mỗi đối tượng vật lý trong Godot có hai thuộc tính bitmask:
- Layer = "Tôi tồn tại trên các layer này" (tôi là gì)
- Mask = "Tôi quét các layer này" (tôi có thể thấy / trúng gì)
Quy tắc then chốt
A va chạm với B khi mask của A chứa layer của B HOẶC mask của B chứa layer của A. Chỉ cần một bên "thấy" bên kia là va chạm sẽ xảy ra.
Đặt tên layer trong Project Settings
Trước khi viết bất kỳ dòng code nào, hãy đặt tên cho các layer của bạn. Việc này giúp Inspector dễ sử dụng hơn rất nhiều và tránh nhầm lẫn khi dự án phát triển lớn hơn.
Project Settings > Layer Names > 2D Physics (hoặc 3D Physics):
| Layer # | Tên | Mục đích |
|---|---|---|
| 1 | Player | Nhân vật người chơi |
| 2 | Enemy | Tất cả thân kẻ địch |
| 3 | Environment | Tường, sàn, nền tảng |
| 4 | Projectile | Đạn, mũi tên, phép thuật |
| 5 | Pickup | Vật phẩm, tiền xu, gói hồi máu |
| 6 | Trigger | Cảm biến, vùng spawn, checkpoint |
Thiết lập layer bằng code (GDScript)
Godot 4 cung cấp hai cách tiếp cận — API dựa trên giá trị (khuyến nghị) và gán bitmask trực tiếp:
# 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)
Bẫy bitmask
Số layer trong set_collision_layer_value() bắt đầu từ 1, nhưng bitmask bên dưới bắt đầu từ 0. Layer 1 = bit 0 = giá trị 1, Layer 2 = bit 1 = giá trị 2, Layer 3 = bit 2 = giá trị 4. Khi phân vân, hãy dùng API dựa trên giá trị để tránh sai sót.
Các sơ đồ layer thường gặp
Platformer / Side-scroller
| Đối tượng | Layer | Mask | Giải thích |
|---|---|---|---|
| Player | 1 | 2, 3, 5, 6 | Phát hiện kẻ địch, môi trường, vật phẩm, trigger |
| Enemy | 2 | 1, 3 | Phát hiện người chơi và môi trường |
| Environment | 3 | — | Thụ động — bị đối tượng khác phát hiện |
| Projectile | 4 | 2, 3 | Trúng kẻ địch và tường |
| Pickup | 5 | — | Thụ động — người chơi phát hiện nó |
| Trigger | 6 | 1 | Chỉ phát hiện người chơi |
Top-down / Roguelike
| Đối tượng | Layer | Mask | Giải thích |
|---|---|---|---|
| Player | 1 | 2, 3, 5, 6 | Phát hiện kẻ địch, tường, NPC, cảm biến |
| Enemy | 2 | 1, 3 | Phát hiện người chơi và tường |
| Wall | 3 | — | Thụ động |
| Bullet | 4 | 1, 2, 3 | Trúng người chơi, kẻ địch và tường |
| NPC | 5 | 3 | Chỉ va chạm với tường |
| Sensor | 6 | 1 | Phát hiện khi người chơi đi vào |
Layer của Area2D / Area3D
Area dùng chung hệ thống layer/mask. Ngoài ra, chúng còn có hai thuộc tính bật/tắt:
-
monitoring— Nếutrue, Area này chủ động phát hiện các đối tượng khác đi vào nó. -
monitorable— Nếutrue, các Area khác có thể phát hiện Area này.
Signal: area_entered vs body_entered
body_entered được kích hoạt khi một PhysicsBody (CharacterBody, RigidBody, StaticBody) đi vào Area. area_entered được kích hoạt khi một Area khác đi vào. Hãy đảm bảo bạn kết nối đúng signal cho trường hợp sử dụng của mình.
# 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 của RayCast
Node RayCast chỉ có mask (không có layer) vì chúng là bộ dò tìm, không phải thân vật lý. Thiết lập mask để lọc những gì tia có thể trúng:
# 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
Ví dụ thực tế: Player – Enemy – Projectile
Đây là một thiết lập layer hoàn chỉnh cho một game hành động điển hình. Các chú thích cho thấy lý do đằng sau mỗi lựa chọn:
# 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
Mẹo: Ngăn bắn nhầm đồng đội
Lưu ý rằng đạn của Player mask layer 2 (Enemy) nhưng không mask layer 1 (Player), và đạn của Enemy mask layer 1 (Player) nhưng không mask layer 2 (Enemy). Đây là cách bạn ngăn bắn nhầm đồng đội hoàn toàn thông qua cấu hình layer — không cần code.
Mẹo gỡ lỗi
- Hiển thị collision shape: Trong editor, vào Debug > Visible Collision Shapes để vẽ tất cả collision shape lúc chạy. Điều này lập tức phát hiện các collider bị thiếu hoặc lệch vị trí.
- Kiểm tra qua Inspector: Chọn một node và mở rộng Collision > Layer và Collision > Mask trong Inspector. Di chuột lên từng bit để xem tên của nó (nếu bạn đã đặt tên trong Project Settings).
-
In ra lúc chạy:
print("Layer: ", collision_layer, " Mask: ", collision_mask)để kiểm tra giá trị bitmask trong lúc chơi.
Các thay đổi khi chuyển từ 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) |
Tham số bit bắt đầu từ 0
|
Tham số layer bắt đầu từ 1
|
| Có 20 layer khả dụng | Có 32 layer khả dụng |
Cạm bẫy khi chuyển đổi
Nếu bạn đang chuyển một dự án Godot 3, hãy nhớ rằng set_collision_layer_bit(0, true) trở thành set_collision_layer_value(1, true). Chỉ số dịch đi +1. Bỏ sót điều này thì tất cả các layer của bạn sẽ bị lệch đi một bậc.
Các lỗi thường gặp
1. Quên thiết lập mask
Đối tượng của bạn có layer nhưng mask lại trống (toàn số 0). Đối tượng tồn tại trong thế giới vật lý nhưng không phát hiện được gì cả. Các đối tượng khác có mask khớp vẫn phát hiện được nó, nhưng move_and_slide() trên đối tượng này sẽ xuyên qua mọi thứ.
2. Nhầm lẫn layer với mask
Thiết lập layer của Player thành 2 (Enemy) thay vì mask của nó. Giờ đây Player chính là một kẻ địch xét theo góc nhìn của engine vật lý. Luôn ghi nhớ: layer = tôi là gì, mask = tôi quét gì.
3. Dùng giá trị bitmask thay vì số layer
Viết set_collision_layer_value(4, true) với suy nghĩ rằng nó thiết lập giá trị bitmask 4 (layer 1+2). Thực tế, nó bật layer 4. API dựa trên giá trị nhận số layer, không phải giá trị bit.
4. Phát hiện một chiều khi mong đợi hai chiều
Đối tượng A mask layer của B nhưng B không mask layer của A. move_and_slide() trên A sẽ va chạm với B, nhưng move_and_slide() trên B sẽ xuyên qua A. Để hai node CharacterBody chặn lẫn nhau, cả hai đều cần có layer của bên kia trong mask của mình.
Tự động hóa thiết lập vật lý với Godot MCP Pro
Đừng bật/tắt các ô layer thủ công nữa. Hãy để AI cấu hình toàn bộ thiết lập collision của bạn trong vài giây — bao gồm đặt tên layer, gán mask và thêm raycast.
Nhận Godot MCP Pro — $15setup_collision
set_physics_layers
get_physics_layers
get_collision_info
add_raycast