为什么碰撞图层很重要
碰撞图层是 Godot 4 中所有物理交互的基础 — 包括移动、命中检测、射线投射、Area 触发器等等。理解 图层(Layer) 与 掩码(Mask) 之间的区别至关重要,而误解它正是初学者和资深开发者共同最常遇到的痛点之一。
图层 vs 掩码 — 思维模型
Godot 中的每个物理对象都有两个位掩码属性:
- Layer = 「我存在于这些图层上」(我是什么)
- Mask = 「我扫描这些图层」(我能看到 / 命中什么)
关键规则
当 A 的掩码包含 B 的图层,或者 B 的掩码包含 A 的图层时,A 与 B 发生碰撞。只要有一方能「看到」另一方,碰撞就会发生。
在项目设置中为图层命名
在编写任何代码之前,先为你的图层命名。这会让检查器(Inspector)更易于使用,并在项目变大时防止混乱。
项目设置 > 图层名称 > 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 = bit 0 = 值 1,图层 2 = bit 1 = 值 2,图层 3 = bit 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 | 仅检测玩家 |
俯视角 / 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 使用相同的图层/掩码系统。此外,它们还有两个开关属性:
-
monitoring— 若为true,该 Area 会主动检测进入其中的其他对象。 -
monitorable— 若为true,其他 Area 可以检测到该 Area。
信号:area_entered vs 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)(没有图层),因为它们是检测器,而不是物理体。设置掩码以过滤射线可以命中的目标:
# 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. 忘记设置掩码
你的对象有图层,但掩码为空(全为零)。该对象在物理世界中存在,却不检测任何东西。掩码匹配的其他对象仍然能检测到它,但该对象上的 move_and_slide() 会穿过一切。
2. 混淆图层与掩码
把玩家的图层(Layer)而非掩码(Mask)设为 2(Enemy)。现在从物理引擎的角度看,玩家就是一个敌人。永远记住:图层 = 我是什么,掩码 = 我扫描什么。
3. 用位掩码值代替图层编号
写 set_collision_layer_value(4, true) 时以为它设置的是位掩码值 4(图层 1+2)。实际上,它启用的是图层 4。基于值的 API 接受的是图层编号,而不是位值。
4. 期望双向检测却只得到单向
对象 A 的掩码包含 B 的图层,但 B 的掩码不包含 A 的图层。A 上的 move_and_slide() 会与 B 碰撞,但 B 上的 move_and_slide() 会穿过 A。要让两个 CharacterBody 节点相互阻挡,双方都需要把对方的图层放进自己的掩码。
用 Godot MCP Pro 自动化物理设置
别再手动切换图层复选框了。让 AI 在几秒内配置好你的整个碰撞设置 — 包括为图层命名、分配掩码以及添加射线投射。
获取 Godot MCP Pro — $15setup_collision
set_physics_layers
get_physics_layers
get_collision_info
add_raycast