概要
Godot Engineでのゲーム開発において、オブジェクト間のインタラクション、特に衝突判定は避けて通れない要素です。「プレイヤーは壁を通り抜けたくない」「敵の攻撃はプレイヤーにだけ当たり、敵同士はすり抜けてほしい」「特定の鍵を持つプレイヤーだけが開けられる扉」…こうした複雑なルールを、もしif文のネストだけで管理しようとすれば、コードは瞬く間にメンテナンス不可能な状態に陥ります。
この問題をエレガントに解決するためにGodotが提供するのが、Collision Layers(衝突レイヤー) とCollision Masks(衝突マスク) という強力なシステムです。
この記事では、多くの開発者が一度は混乱するこの2つの概念を、具体的なシナリオとコード例を交えて解説します。
なぜCollision Layer/Maskが必要なのか
小規模なプロトタイプでは、オブジェクトの種類も少なく、単純な衝突判定で十分かもしれません。しかし、プロジェクトが成長するにつれて、以下のような要求が生まれます。
- パフォーマンスの最適化: 全てのオブジェクトが他の全てのオブジェクトと衝突判定を行う のは、CPUにとって大きな無駄です。プレイヤーがコインと衝突する必要はあっても、コインが壁と衝突する必要はありません。判定対象を絞ることで、ゲーム全体のパフォーマンスが向上します。
- 宣言的な設計: 「何が」「何と」衝突するのかを、コードではなくインスペクタ上で宣言的に設定できるため、ロジックが追いやすくなります。
- 複雑なインタラクションの実装: 特定の条件下でのみ発生するインタラクション(例:パワーアップ中だけ敵を倒せる)を、コードを複雑化させることなく実現できます。
基本概念:LayerとMaskの役割分担
CollisionObject2D/3D(Area2D, CharacterBody2D, RigidBody2Dなど)のインスペクタを見ると、CollisionセクションにLayerとMaskという項目があります。この2つの役割を正確に理解することが最初のステップです。
- Layer (レイヤー): 「私はここにいます」という自己紹介の名札です。オブジェクトがどの物理グループに属しているかを示します。
- Mask (マスク): 「私はこれと相互作用したい」という探している相手のリストです。オブジェクトがどのレイヤーに属するオブジェクトを検出したいかを示します。
衝突が成立する条件は、PhysicsBody同士の場合、片方の関心があれば十分です。つまり、オブジェクトAがオブジェクトBと衝突するためには、以下のいずれかを満たせばOKです。
- オブジェクトAのMaskが 、オブジェクトBのLayerにチェックを入れている。
- オブジェクトBのMaskが、オブジェクトAのLayerにチェックを入れている。
この「どちらか一方が相手を見ていれば衝突する」という点が重要です。双方向である必要はありません。
補足: 双方向でMaskを設定する必要があるのは、「両者がお互いを検知したい場合」だけです。例えば、プレイヤーと敵が両方とも相手の存在を検知したい場合は、双方にMaskを設定します。しかし、物理的な衝突(ぶつかって止まる)自体は、片方のMaskが相手のLayerを見ていれば発生します。
設定の第一歩:レイヤーに名前を付ける
LayerとMaskは内部的にはビットフラグ(数値)ですが、人間が「レイヤー3は敵」などと覚えるのは非効率的で、ミスを誘発します。必ずプロジェクト設定で各レイヤーに分かりやすい名前を付けましょう。
- メニューから「プロジェクト」→「プロジェクト設定」を開きます。
- 左のツリーから「レイヤー名」→「2D物理」または「3D物理」を選択します。
- 各レイヤーに「player」「enemy_body」「enemy_attack」「world」「item」といった具体的な名前を付けます。

この一手間が、後の開発効率を大きく改善します。
実践例:具体的な設定シナリオ
一般的なアクションゲームを例に、具体的な設定を見ていきましょう。
| オブジェクト | Layer (私はここにいる) | Mask (これと衝突/検出したい) | 備考 |
|---|---|---|---|
| Player (CharacterBody2D) | player | world, enemy_body, item, enemy_attack | すべての主要なオブジェクトと相互作用する |
| Enemy (CharacterBody2D) | enemy_body | world, player | 敵同士は衝突せず、アイテムも無視する |
| Enemy Attack (Area2D) | enemy_attack | player | プレイヤーにダメージを与える判定のみを行う |
| World (StaticBody2D) | world | player, enemy_body | プレイヤーと敵の体をブロックする |
| Coin (Area2D) | item | player | プレイヤーによってのみ収集される |
この設定 により、以下の挙動が実現されます。
- プレイヤー: 壁に阻まれ、敵に触れ、アイテムを拾い、敵の攻撃を受けます。
- 敵: 壁に沿って移動し、プレイヤーを追いかけますが、他の敵やアイテム、敵自身の攻撃判定とは干渉しません。
- 敵の攻撃範囲: プレイヤーに侵入されたことだけを検知し、シグナルを発します。壁や他のオブジェクトには影響されません。
コードによる動的な制御
Layer/Maskの真価は、スクリプトから動的に変更することで発揮されます。例えば、「ゴースト」アイテムを取ったプレイヤーが、一定時間敵をすり抜けるようにする実装を見てみましょう。
# player.gd
func _ready():
# 初期状態では敵(enemy_body)と衝突する
set_collision_mask_value(3, true) # 3番目のレイヤーが'enemy_body'だと仮定
func _on_ghost_item_picked_up():
# 敵との衝突を無効化
set_collision_mask_value(3, false)
print("ゴーストモード開始! 敵をすり抜けます。")
# 5秒後に元に戻すタイマー
$GhostTimer.start(5.0)
func _on_GhostTimer_timeout():
# 敵との衝突を再度有効化
set_collision_mask_value(3, true)
print("ゴーストモード終了。")
このようにset_collision_mask_value(layer_number, enabled)を使うことで、ゲームの状態に応じて衝突ルールを柔軟に変更できます。
よくある間違いとベストプラクティス
初心者が陥りがちなミスと、それを避けるためのベストプラクティスをまとめました。
| よくある間違い | ベストプラクティス |
|---|---|
| レイヤーに名前を付けない | 必ず名前を付ける。 チーム開発や長期的なメンテナンスにおいて必須です。 |
Area2DとPhysicsBodyの検知条件を混同する | PhysicsBody同士の衝突は片方のMaskが相手のLayerを含めば成立します(OR条件)。Area2Dも同様に、自身のMaskが相手のLayerを検知すればbody_entered等のシグナルを発します。ただし、双方が検知したい場合は、両方にMaskを設定する必要があります。 |
| 1つのオブジェクトに複数の役割を持たせる | 判定を分ける。例えば、敵の「体」と「攻撃範囲」は別のオブジェクト(CharacterBody2DとArea2D)に分け、それぞれに適切なLayer/Maskを設定します。 |
move_and_slide()の結果をif文で複雑にフィルタリングする | move_and_slide()が衝突を報告する前に、Layer/Maskで不要な衝突を予め除外しておくべきです。コードはシンプルに保ちましょう。 |
| 全32レイヤーを無計画に使い始める | 主要なカテゴリ(player, enemy, world...)から割り当て、補助的な判定(minimap, camera_trigger...)は後ろのレイヤーから使っていくなど、大まかなルールを決めます 。 |
パフォーマンスに関する考慮事項
Godotの物理エンジンは高度に最適化されていますが、オブジェクト数が数千を超えるような状況では、衝突判定がボトルネックになる可能性があります。
- Maskを最小限に: オブジェクトのMaskに設定されたレイヤーが多ければ多いほど、判定対象が増え、負荷が高まります。本当に必要な相互作用だけを有効にしましょう。
- Physics Processの処理を軽く:
_physics_process内で重い処理を行うと、物理演算全体のパフォーマンスに影響します。 - 不要なオブジェクトの無効化: 画面外の敵やオブジェクトは、
CollisionShapeを無効化($CollisionShape2D.disabled = true)するか、ツリーから削除することで、物理演算の対象から完全に外すことができます。
次のステップ
Collision Layer/Maskをマスターしたら、次はこれらの関連トピックを学ぶことで、さらに高度なゲームメカニクスを実装できます。
Area2D/Area3D: 物理的な衝突を起こさずに、オブジェクトの侵入・退出を検出するのに最適です。索敵範囲、ダメージゾーン、会話イベントのトリガーなどに活用できます。- Ray Casting:
RayCast2D/RayCast3DノードやPhysicsDirectSpaceStateを使い、特定の方向に光線(レイ)を飛ばして、最初に衝突したオブジェクトを検出します。 - Physics Material:
PhysicsMaterialをPhysicsBodyやAreaに設定することで、摩擦係数や反発係数(跳ね返り)を細かく制御できます。
まとめ
Collision LayersとMasksは、Godotにおける物理インタラクションの根幹をなす、非常に強力でスケーラブルなシステムです。「Layerは名札、Maskは探す相手のリスト」という役割分担と、「片方のMaskが相手のLayerを見ていれば衝突が成立する」というOR条件のルールを覚えれば、あとは実践あるのみです。
インスペクタ上で宣言的に関係性を定義し、必要な場合のみスクリプトで動的に制御する。この原則を守ることで、プロジェクトはよりクリーンに、よりパフォーマンス良く、そしてより管理しやすくなるでしょう。まずはプロジェクト設定でレイヤーに名前を付けることから始めてみてください。