Layers = what you ARE. "I appear on layer 2 (Player)."
Masks = what you SCAN FOR. "I detect things on layers 1 and 3."
The key insight: An object only detects/collides with things on layers included in its own mask. You usually set masks on the thing doing the detecting—the player, bullets, raycasts, and Area2D sensors. Static walls typically just sit on a layer and don't need masks.
Wall: Layer 1, Mask (none) → Exists but doesn't detect anything
Player: Layer 2, Mask 1,3 → Detects walls and enemies
Enemy: Layer 3, Mask 1,2 → Detects walls and player
"Why Does My Bullet Go Through the Enemy?"
This is the #2 most common beginner question (after movement). The answer is almost always layers/masks:
Problem: Bullet and Enemy both on Layer 1, both scan Mask 1
Result: They detect each other... but so does everything else
Fix: Use dedicated layers
Recommended Layer Setup
Layer 1: World (walls, floors, obstacles)
Layer 2: Player
Layer 3: Enemies
Layer 4: Player Projectiles
Layer 5: Enemy Projectiles
Layer 6: Pickups
Then configure masks:
- Player: Mask 1 (world), 3 (enemies), 5 (enemy bullets), 6 (pickups)
- Enemy: Mask 1 (world), 2 (player), 4 (player bullets)
- Player Bullet: Mask 1 (world), 3 (enemies) — NOT 2 (won't hit player)
- Pickup: Mask 2 (player only)
Name your layers in Project Settings → General → Layer Names → 2D Physics to keep track.
Setting Layers/Masks in Code
# Set layer (what you are)
set_collision_layer_value(1, true) # On layer 1
set_collision_layer_value(2, false) # Not on layer 2
# Set mask (what you detect)
set_collision_mask_value(2, true) # Detect layer 2
Bitmask Shorthand (Readable Version)
Raw numbers like collision_mask = 10 become cryptic when you change layers. Use bit operations for intent that reads clearly:
# Define your layers as constants
const L_WORLD := 1
const L_PLAYER := 2
const L_ENEMY := 3
const L_PLAYER_BULLET := 4
# Set layer (what you are)
collision_layer = 1 << (L_PLAYER - 1) # Layer 2
# Set mask (what you detect) - combine with |
collision_mask = (1 << (L_WORLD - 1)) | (1 << (L_ENEMY - 1)) # Layers 1 and 3
This reads like intent ("I detect world and enemies") rather than numerology.