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 layermask 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:

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
1PlayerNhân vật người chơi
2EnemyTất cả thân kẻ địch
3EnvironmentTường, sàn, nền tảng
4ProjectileĐạn, mũi tên, phép thuật
5PickupVật phẩm, tiền xu, gói hồi máu
6TriggerCả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
Player12, 3, 5, 6Phát hiện kẻ địch, môi trường, vật phẩm, trigger
Enemy21, 3Phát hiện người chơi và môi trường
Environment3Thụ động — bị đối tượng khác phát hiện
Projectile42, 3Trúng kẻ địch và tường
Pickup5Thụ động — người chơi phát hiện nó
Trigger61Chỉ phát hiện người chơi

Top-down / Roguelike

Đối tượng Layer Mask Giải thích
Player12, 3, 5, 6Phát hiện kẻ địch, tường, NPC, cảm biến
Enemy21, 3Phát hiện người chơi và tường
Wall3Thụ động
Bullet41, 2, 3Trúng người chơi, kẻ địch và tường
NPC53Chỉ va chạm với tường
Sensor61Phá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:

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

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 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 , 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 — $15
Công cụ liên quan: setup_collision set_physics_layers get_physics_layers get_collision_info add_raycast