概要
Godotの衝突判定システムは、Layers(レイヤー)とMasks(マスク)を使って、どのオブジェクト同士が衝突するかを制御します。 適切に設定することで、パフォーマンス向上と意図しない衝突の防止が可能です。
最初はこのシステムが全く理解できず、「なぜプレイヤーの攻撃が自分に当たるのか」「敵同士が押し合ってバグる」といった問題に悩まされました。レイヤーとマスクの仕組みを理解した今では、ゲームの衝突判定を綺麗に整理できるようになりました。

基本概念
Collision Layer(レイヤー)
- 意味: このオブジェクトが「どの層に存在するか」
- 設定: 複数選択可能(ビットマスク)
Collision Mask(マスク)
- 意味: このオブジェクトが「どの層と衝突判定を行うか」
- 設定: 複数選択可能(ビットマスク)
実装例:ゲームでの設定
レイヤー構成例
Layer 1: Player - プレイヤーキャラクター
Layer 2: Enemies - 敵キャラクター
Layer 3: PlayerWeapon - プレイヤーの攻撃判定
Layer 4: EnemyWeapon - 敵の攻撃判定
Layer 5: Environment - 壁、床、障害物
Layer 6: Pickups - アイテム、コイン
各オブジェクトの設定
プレイヤー
- Layer: 1 (Player)
- Mask: 2, 4, 5, 6 (Enemies, EnemyWeapon, Environment, Pickups)
- 結果: 敵、敵の攻撃、環境、アイテムと衝突
敵
- Layer: 2 (Enemies)
- Mask: 1, 3, 5 (Player, PlayerWeapon, Environment)
- 結果: プレイヤー、プレイヤーの攻撃、環境と衝突(敵同士はすり抜ける)
プレイヤーの武器
- Layer: 3 (PlayerWeapon)
- Mask: 2 (Enemies)
- 結果: 敵だけに当たる(プレイヤー自身には当たらない)
プロジェクト設定でレイヤー名を付ける
- プロジェクト設定 → Layer Names → 2D Physics
- 各レイヤーに分かりやすい名前を設定

Layer 1: Player
Layer 2: Enemies
Layer 3: PlayerWeapon
Layer 4: EnemyWeapon
Layer 5: Environment
Layer 6: Pickups
Layer 7: Triggers
Layer 8: Platforms
コードでの制御
レイヤーとマスクの設定
func _ready():
# レイヤーの設定(ビット演算)
collision_layer = 1 # Layer 1のみ
collision_layer = 5 # Layer 1と3(1 + 4 = 5)
# マスクの設定
collision_mask = 2 # Layer 2のみ
collision_mask = 14 # Layer 2,3,4(2 + 4 + 8 = 14)
# 個別のビット操作
set_collision_layer_bit(0, true) # Layer 1を有効
set_collision_mask_bit(1, false) # Layer 2との衝突を無効
動的な衝突制御
# 無敵時間の実装
func become_invincible():
# 敵の攻撃レイヤーとの衝突を無効化
set_collision_mask_bit(3, false) # Layer 4 (EnemyWeapon)を無効
await get_tree().create_timer(invincibility_duration).timeout
# 衝突を再度有効化
set_collision_mask_bit(3, true)
実践的な活用例
片道プラットフォーム
# OneWayPlatform.gd
extends StaticBody2D
func _ready():
# 下からは通り抜け、上からは乗れる
one_way_collision = true
func allow_pass_through():
# 一時的に衝突を無効化
set_collision_layer_bit(7, false) # Platformsレイヤーを無効
await get_tree().create_timer(0.5).timeout
set_collision_layer_bit(7, true)
フェーズシフト敵
# PhasingEnemy.gd
extends CharacterBody2D
func phase_shift():
# 物理世界から一時的に消える
collision_layer = 0 # 全レイヤーから除外
collision_mask = 0 # 全ての衝突を無視
modulate.a = 0.5 # 半透明に
await get_tree().create_timer(2.0).timeout
# 元に戻る
collision_layer = 2 # Enemiesレイヤー
collision_mask = 5 # Player + Environment
modulate.a = 1.0
トラブルシューティング
よくある問題と解決法
問題:プレイヤーの攻撃が自分に当たる
# 解決:武器のマスクからプレイヤーレイヤーを除外
weapon_area.collision_mask = 2 # Enemiesのみ
問題:敵同士が押し合う
# 解決:敵のマスクから敵レイヤーを除外
enemy.collision_mask = 5 # Player + Environment(Enemiesを含まない)
パフォーマンス最適化
- 不要な判定を避ける: 相互作用しないオブジェクトは別レイヤーに
- レイヤー数を最小限に: 32レイヤーまで使用可能だが、必要最小限に
- 動的な切り替えは慎重に: 頻繁な変更は処理負荷になる
ベストプラクティス
- レイヤー構成 をドキュメント化
- 定数でレイヤー番号を管理
- デバッグ表示で衝突を可視化
# Constants.gd
class_name GameConstants
const LAYER_PLAYER = 0
const LAYER_ENEMIES = 1
const LAYER_PLAYER_WEAPON = 2
const LAYER_ENEMY_WEAPON = 3
const LAYER_ENVIRONMENT = 4
実装して学んだこと
レイヤーとマスクの概念は最初は難しく感じましたが、「レイヤー=自分がどこに属しているか」「マスク=何と衝突したいか」というシンプルな考え方で理解できるようになりました。
特に便利だと感じたのは、無敵時間の実装です。以前はフラグ管理で複雑になっていましたが、マスクのビットを切り替えるだけで実現できるようになりました。
また、プロジェクト設定でレイヤーに名前を付けられるのも大きな発見でした。数字ではなく意味のある名前で管理できるので、チーム開発でも理解しやすくなります。