Почему слои коллизий важны
Слои коллизий — это основа всех физических взаимодействий в Godot 4 — движения, обнаружения попаданий, рейкастинга, триггеров Area и не только. Понимание разницы между Layer и Mask крайне важно, а её недопонимание — одна из самых распространённых проблем как для новичков, так и для опытных разработчиков.
Layer против Mask — ментальная модель
У каждого физического объекта в Godot есть два свойства-битмаски:
- Layer = «Я существую на этих слоях» (то, чем я являюсь)
- Mask = «Я сканирую эти слои» (то, что я могу видеть / поразить)
Ключевое правило
A сталкивается с B, когда Mask объекта A включает Layer объекта B ИЛИ Mask объекта B включает Layer объекта A. Достаточно, чтобы только одна сторона «видела» другую, чтобы произошло столкновение.
Именование слоёв в настройках проекта
Прежде чем писать какой-либо код, дайте имена своим слоям. Это делает инспектор гораздо удобнее в использовании и предотвращает путаницу по мере роста проекта.
Настройки проекта > Имена слоёв > 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 | Обнаруживает только игрока |
Вид сверху / рогалик
| Объект | 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
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()
Маска коллизий RayCast
У узлов RayCast есть только Mask (нет Layer), потому что они являются детекторами, а не физическими телами. Задайте маску, чтобы отфильтровать, по чему может попадать луч:
# 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
Совет: предотвращение дружественного огня
Обратите внимание, что пули игрока маскируют слой 2 (Enemy), но не слой 1 (Player), а пули врагов маскируют слой 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, но B не маскирует слой A. move_and_slide() на A столкнётся с B, но move_and_slide() на B пройдёт сквозь A. Чтобы два узла CharacterBody блокировали друг друга, оба должны иметь слой другого в своей маске.
Автоматизируйте настройку физики с Godot MCP Pro
Прекратите вручную переключать флажки слоёв. Позвольте ИИ настроить всю вашу систему коллизий за секунды — включая именование слоёв, назначение масок и добавление рейкастов.
Получить Godot MCP Pro — $15setup_collision
set_physics_layers
get_physics_layers
get_collision_info
add_raycast