【Godot】Collision LayersとMasksによる衝突判定の整理

作成: 2025-06-20最終更新: 2025-12-16

Godot EngineのCollision LayersとMasksの仕組みを解説。効率的な設定方法から、コードによる動的制御、パフォーマンス最適化まで紹介します。

概要

Godot Engineでのゲーム開発において、オブジェクト間のインタラクション、特に衝突判定は避けて通れない要素です。「プレイヤーは壁を通り抜けたくない」「敵の攻撃はプレイヤーにだけ当たり、敵同士はすり抜けてほしい」「特定の鍵を持つプレイヤーだけが開けられる扉」…こうした複雑なルールを、もしif文のネストだけで管理しようとすれば、コードは瞬く間にメンテナンス不可能な状態に陥ります。

この問題をエレガントに解決するためにGodotが提供するのが、Collision Layers(衝突レイヤー)Collision Masks(衝突マスク) という強力なシステムです。

この記事では、多くの開発者が一度は混乱するこの2つの概念を、具体的なシナリオとコード例を交えて解説します。

なぜCollision Layer/Maskが必要なのか

小規模なプロトタイプでは、オブジェクトの種類も少なく、単純な衝突判定で十分かもしれません。しかし、プロジェクトが成長するにつれて、以下のような要求が生まれます。

  • パフォーマンスの最適化: 全てのオブジェクトが他の全てのオブジェクトと衝突判定を行うのは、CPUにとって大きな無駄です。プレイヤーがコインと衝突する必要はあっても、コインが壁と衝突する必要はありません。判定対象を絞ることで、ゲーム全体のパフォーマンスが向上します。
  • 宣言的な設計: 「何が」「何と」衝突するのかを、コードではなくインスペクタ上で宣言的に設定できるため、ロジックが追いやすくなります。
  • 複雑なインタラクションの実装: 特定の条件下でのみ発生するインタラクション(例:パワーアップ中だけ敵を倒せる)を、コードを複雑化させることなく実現できます。

基本概念:LayerとMaskの役割分担

CollisionObject2D/3DArea2D, CharacterBody2D, RigidBody2Dなど)のインスペクタを見ると、CollisionセクションにLayerMaskという項目があります。この2つの役割を正確に理解することが最初のステップです。

  • Layer (レイヤー): 「私はここにいます」という自己紹介の名札です。オブジェクトがどの物理グループに属しているかを示します。
  • Mask (マスク): 「私はこれと相互作用したい」という探している相手のリストです。オブジェクトがどのレイヤーに属するオブジェクトを検出したいかを示します。

衝突が成立する条件は、PhysicsBody同士の場合、片方の関心があれば十分です。つまり、オブジェクトAがオブジェクトBと衝突するためには、以下のいずれかを満たせばOKです。

  1. オブジェクトAのMaskが、オブジェクトBのLayerにチェックを入れている。
  2. オブジェクトBのMaskが、オブジェクトAのLayerにチェックを入れている。

この「どちらか一方が相手を見ていれば衝突する」という点が重要です。双方向である必要はありません。

補足: 双方向でMaskを設定する必要があるのは、「両者がお互いを検知したい場合」だけです。例えば、プレイヤーと敵が両方とも相手の存在を検知したい場合は、双方にMaskを設定します。しかし、物理的な衝突(ぶつかって止まる)自体は、片方のMaskが相手のLayerを見ていれば発生します。


設定の第一歩:レイヤーに名前を付ける

LayerとMaskは内部的にはビットフラグ(数値)ですが、人間が「レイヤー3は敵」などと覚えるのは非効率的で、ミスを誘発します。必ずプロジェクト設定で各レイヤーに分かりやすい名前を付けましょう。

  1. メニューから「プロジェクト」→「プロジェクト設定」を開きます。
  2. 左のツリーから「レイヤー名」→「2D物理」または「3D物理」を選択します。
  3. 各レイヤーに「player」「enemy_body」「enemy_attack」「world」「item」といった具体的な名前を付けます。
Collision Layer名前設定

この一手間が、後の開発効率を大きく改善します。

実践例:具体的な設定シナリオ

一般的なアクションゲームを例に、具体的な設定を見ていきましょう。

オブジェクトLayer (私はここにいる)Mask (これと衝突/検出したい)備考
Player (CharacterBody2D)playerworld, enemy_body, item, enemy_attackすべての主要なオブジェクトと相互作用する
Enemy (CharacterBody2D)enemy_bodyworld, player敵同士は衝突せず、アイテムも無視する
Enemy Attack (Area2D)enemy_attackplayerプレイヤーにダメージを与える判定のみを行う
World (StaticBody2D)worldplayer, enemy_bodyプレイヤーと敵の体をブロックする
Coin (Area2D)itemplayerプレイヤーによってのみ収集される

この設定により、以下の挙動が実現されます。

  • プレイヤー: 壁に阻まれ、敵に触れ、アイテムを拾い、敵の攻撃を受けます。
  • : 壁に沿って移動し、プレイヤーを追いかけますが、他の敵やアイテム、敵自身の攻撃判定とは干渉しません。
  • 敵の攻撃範囲: プレイヤーに侵入されたことだけを検知し、シグナルを発します。壁や他のオブジェクトには影響されません。

コードによる動的な制御

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)を使うことで、ゲームの状態に応じて衝突ルールを柔軟に変更できます。


よくある間違いとベストプラクティス

初心者が陥りがちなミスと、それを避けるためのベストプラクティスをまとめました。

よくある間違いベストプラクティス
レイヤーに名前を付けない必ず名前を付ける。 チーム開発や長期的なメンテナンスにおいて必須です。
Area2DPhysicsBodyの検知条件を混同するPhysicsBody同士の衝突は片方のMaskが相手のLayerを含めば成立します(OR条件)。Area2Dも同様に、自身のMaskが相手のLayerを検知すればbody_entered等のシグナルを発します。ただし、双方が検知したい場合は、両方にMaskを設定する必要があります。
1つのオブジェクトに複数の役割を持たせる判定を分ける。例えば、敵の「体」と「攻撃範囲」は別のオブジェクト(CharacterBody2DArea2D)に分け、それぞれに適切な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: PhysicsMaterialPhysicsBodyAreaに設定することで、摩擦係数や反発係数(跳ね返り)を細かく制御できます。

まとめ

Collision LayersとMasksは、Godotにおける物理インタラクションの根幹をなす、非常に強力でスケーラブルなシステムです。「Layerは名札、Maskは探す相手のリスト」という役割分担と、「片方のMaskが相手のLayerを見ていれば衝突が成立する」というOR条件のルールを覚えれば、あとは実践あるのみです。

インスペクタ上で宣言的に関係性を定義し、必要な場合のみスクリプトで動的に制御する。この原則を守ることで、プロジェクトはよりクリーンに、よりパフォーマンス良く、そしてより管理しやすくなるでしょう。まずはプロジェクト設定でレイヤーに名前を付けることから始めてみてください。