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

作成: 2025-06-20最終更新: 2026-07-07

GodotのCollision LayerとCollision Maskを、プレイヤー、敵、壁、攻撃判定、アイテム取得の実例から整理します。反応しない原因を見つけるチェック手順も解説します。

プレイヤーは壁にぶつかる。敵の攻撃はプレイヤーだけに当たる。プレイヤーの攻撃は敵だけに当たる。コインはプレイヤーだけが拾える。

ゲームを作っていると、こうした「何が何に反応するか」を大量に管理することになります。そこで使うのが Collision LayerCollision Mask です。

Collision Layerは自分の所属、Collision Maskは検出したい相手を表す図

この記事では、Layer/Maskを単なるチェックボックスとしてではなく、ゲーム全体の当たり判定を整理するための設計道具として解説します。

この記事でわかること

  • LayerとMaskの役割の違い
  • プレイヤー、敵、壁、攻撃、アイテムの実践的な分け方
  • 攻撃判定やアイテム取得での設定例
  • 衝突や検出が反応しないときの確認手順

Sponsored

Layerは所属、Maskは見に行く相手

Layer/Maskで最初に混乱しやすいのは、どちらも同じようなチェックボックスに見えることです。しかし役割はまったく違います。

項目意味考え方
Collision Layer自分が所属している物理レイヤー「私はPlayerです」「私はEnemyです」
Collision Mask自分が検出したい相手のLayer「私はWorldとEnemyを見ます」

Layerは名札です。プレイヤーなら Player レイヤー、敵なら Enemy レイヤー、壁なら World レイヤーに所属します。

Maskは探しに行く相手です。プレイヤーが壁にぶつかりたいなら、プレイヤーのMaskに World を入れます。プレイヤーの攻撃が敵だけに当たってほしいなら、攻撃判定のMaskに EnemyHurtBox を入れます。

インスペクタでは、同じ Collision セクションの中にLayerとMaskが並びます。上が自分の所属、下が見に行く相手です。

CollisionObject2DのLayerとMask設定画面

重要なのは、判定したい側のMaskが、相手のLayerを見ているか です。

PlayerAttackのMask  ->  EnemyHurtBoxのLayer

この関係ができていれば、PlayerAttack側はEnemyHurtBoxを検出できます。反対に、EnemyHurtBox側のMaskがPlayerAttackを見ていなくても、PlayerAttack側のシグナルで処理する設計なら十分です。

両方にMaskを入れる必要があるのは、両方のオブジェクトがそれぞれ相手を検出したい場合です。例えば「敵もプレイヤーを検出し、プレイヤー側も敵を検出する」ような設計なら双方に設定します。攻撃判定のように、片方のArea2Dだけが反応すればよい場面では、検出する側のMaskを正しく設定することが先です。

まずレイヤーに名前を付ける

Godotのレイヤーは1番から32番まであります。初期状態では数字だけなので、開発が進むほど「3番って敵だっけ、アイテムだっけ」と迷いやすくなります。

最初に「プロジェクト設定」で名前を付けておくと、インスペクタのLayer/Maskが読みやすくなります。

  1. 「プロジェクト」から「プロジェクト設定」を開く
  2. 「レイヤー名」から「2D物理」を選ぶ
  3. 使うレイヤーに名前を付ける
プロジェクト設定で2D物理レイヤーに名前を付ける画面

たとえば2Dアクションなら、最初は次のような名前で十分です。

番号名前用途
1World床、壁、地形
2Playerプレイヤー本体
3Enemy敵本体
4PlayerHitBoxプレイヤーの攻撃判定
5EnemyHurtBox敵がダメージを受ける範囲
6EnemyHitBox敵の攻撃判定
7PlayerHurtBoxプレイヤーがダメージを受ける範囲
8Itemアイテム、コイン、回復薬

最初から32個を完璧に埋める必要はありません。むしろ、使いそうな主要カテゴリだけ決めて、後から必要になったものを追加するほうが破綻しにくいです。

Sponsored

アクションRPGのレイヤー設計例

Layer/Maskは、個別ノードの設定というより「ゲーム内の相互作用の表」です。どのオブジェクトが、どの相手を見るべきかを先に整理すると、設定ミスが減ります。

アクションRPGでのLayer/Mask相互作用マトリクス

アクションRPGの一例として、次のように考えます。

オブジェクトLayerMask目的
プレイヤー本体PlayerWorld, Enemy壁に止められ、敵本体にも接触する
敵本体EnemyWorld, Player壁に止められ、プレイヤーと接触する
壁、床、地形World必要に応じて空でもよい多くの場合、動く側がWorldを見る
プレイヤー攻撃PlayerHitBoxEnemyHurtBox敵のダメージ判定だけを検出する
敵攻撃EnemyHitBoxPlayerHurtBoxプレイヤーのダメージ判定だけを検出する
アイテムItemPlayerプレイヤーが入ったときだけ拾われる

この表で大切なのは、敵本体敵がダメージを受ける範囲 を分けていることです。

敵本体は移動や壁との衝突に使います。敵のHurtBoxは「攻撃を受けるためのセンサー」です。この2つを同じ判定にしてしまうと、プレイヤーの攻撃が敵の移動ボディに当たるのか、ダメージ用のArea2Dに当たるのかが曖昧になります。

小さなゲームなら本体だけでも動きます。しかし、ノックバック、盾、弱点部位、当たり判定の一時無効化などを入れ始めると、体と攻撃/被ダメージ判定を分けておくほうが圧倒的に管理しやすくなります。

実装例1: アイテムをプレイヤーだけが拾う

まずはシンプルな例です。回復薬やコインのようなアイテムは、敵や弾には反応せず、プレイヤーが入ったときだけ拾われてほしいはずです。

ノード構成は次のようにします。

PotionPickup (Area2D)
└─ CollisionShape2D

インスペクタでは、次のように設定します。

項目設定
LayerItem
MaskPlayer
Signalbody_entered を接続

Area2D は物理的に押し返すノードではありません。プレイヤーが範囲に入ったことを検出して、拾う処理を呼ぶセンサーです。

extends Area2D

@export var heal_amount := 20

func _ready() -> void:
    # body_enteredはCharacterBody2DなどのPhysicsBody2Dが入ったときに発火します。
    body_entered.connect(_on_body_entered)

func _on_body_entered(body: Node2D) -> void:
    # Layer/MaskでPlayerだけに絞っていても、最後に役割を確認すると安全です。
    if not body.is_in_group("player"):
        return

    # 実際の回復処理はプレイヤー側のメソッドに任せます。
    body.heal(heal_amount)
    queue_free()

ここでLayer/Maskとグループの役割が分かれます。

Layer/Maskは、Godotの物理エンジンに「そもそもPlayer以外は候補にしないで」と伝えるための設定です。グループは、スクリプト側で「このNodeはゲーム上のプレイヤーとして扱ってよいか」を確認するための保険です。

実装例2: 攻撃判定をHitBoxとHurtBoxに分ける

アクションゲームでは、攻撃する側の判定を HitBox、ダメージを受ける側の判定を HurtBox と分けることがよくあります。

PlayerHitBoxがEnemyHurtBoxを検出し、ダメージ処理へつながる図

プレイヤーの剣攻撃なら、次のような構成にします。

Player (CharacterBody2D)
├─ Sprite2D
├─ CollisionShape2D
└─ SwordHitBox (Area2D)
   └─ CollisionShape2D

敵側には、ダメージを受けるためのArea2Dを置きます。

Enemy (CharacterBody2D)
├─ CollisionShape2D
└─ HurtBox (Area2D)
   └─ CollisionShape2D

設定例は次のとおりです。

ノードLayerMask
SwordHitBoxPlayerHitBoxEnemyHurtBox
Enemy/HurtBoxEnemyHurtBox空でもよい

攻撃判定を発生させる側が EnemyHurtBox を見に行くので、敵側のMaskは空でも成立します。もちろん、敵側でも「攻撃範囲に入られた」ことを検出したい設計なら、敵側のMaskにも PlayerHitBox を入れます。

extends Area2D

@export var damage := 10

func _ready() -> void:
    # 攻撃判定はArea2D同士の重なりを見るのでarea_enteredを使います。
    area_entered.connect(_on_area_entered)

func enable_for_one_swing() -> void:
    # 攻撃していない間はShapeを無効にして、当たり続けないようにします。
    $CollisionShape2D.disabled = false
    await get_tree().create_timer(0.12).timeout
    $CollisionShape2D.disabled = true

func _on_area_entered(area: Area2D) -> void:
    # Layer/Maskで候補を絞ったあと、グループでゲーム上の役割を確認します。
    if not area.is_in_group("enemy_hurtbox"):
        return

    # ダメージ処理はHurtBox側に受け渡すと、敵本体の構造を変えても壊れにくくなります。
    area.apply_damage(damage)

この形にしておくと、敵本体の CharacterBody2D は移動や壁との衝突に集中できます。ダメージを受ける範囲はHurtBoxが担当します。

たとえばボスの頭だけ弱点にしたい場合は、頭用のHurtBoxだけを enemy_weak_point グループに入れます。盾で前方攻撃だけ防ぐなら、盾用のHurtBoxを別Layerにするか、グループで受けるダメージを変えます。Layer/Maskで大まかに絞り、グループやメソッドでゲームルールを表現する、という分担が使いやすいです。

Sponsored

実装例3: 無敵時間だけ敵の攻撃を無視する

Layer/Maskは、実行中に切り替えることもできます。よくある例は、ダメージを受けた直後の無敵時間です。

プレイヤーのHurtBoxが通常は EnemyHitBox を検出し、無敵中だけ検出しないようにします。

extends Area2D

const LAYER_ENEMY_HITBOX := 6

var invincible := false

func start_invincible_time() -> void:
    if invincible:
        return

    invincible = true

    # 無敵中は敵の攻撃レイヤーをMaskから外し、攻撃Area2Dを検出しないようにします。
    set_collision_mask_value(LAYER_ENEMY_HITBOX, false)

    await get_tree().create_timer(1.0).timeout

    # 無敵が終わったら、再び敵の攻撃を検出できる状態に戻します。
    set_collision_mask_value(LAYER_ENEMY_HITBOX, true)
    invincible = false

この例で変えているのは、プレイヤー本体の移動衝突ではなく、プレイヤーのHurtBoxのMaskです。つまり「壁にぶつかる」「床に立つ」といった移動処理には影響させず、「ダメージ判定だけを一時的に無視する」設計にできます。

本体のCollision Maskを丸ごと変えると、無敵中に壁もすり抜ける、アイテムも拾えない、といった副作用が出ることがあります。何を一時的に無視したいのかを考え、その判定を担当しているArea2Dだけを切り替えるのが安全です。

コードからLayer/Maskを設定する

基本はインスペクタで設定するほうが読みやすいです。ただし、生成した弾や攻撃判定のように、スクリプトから初期化したい場面もあります。

Godotでは set_collision_layer_value()set_collision_mask_value() を使うと、1番から32番のレイヤー番号をそのまま指定できます。

extends Area2D

const LAYER_WORLD := 1
const LAYER_PLAYER := 2
const LAYER_ENEMY := 3

func setup_as_player_bullet() -> void:
    # 既存設定を消してから、弾に必要なLayer/Maskだけを入れます。
    collision_layer = 0
    collision_mask = 0

    # この弾自身はPlayer側の判定として扱います。
    set_collision_layer_value(LAYER_PLAYER, true)

    # 弾は敵と壁だけを見る。味方やアイテムには反応しません。
    set_collision_mask_value(LAYER_ENEMY, true)
    set_collision_mask_value(LAYER_WORLD, true)

collision_layercollision_mask は内部的にはビットマスクの整数です。慣れている人は 1 << 0 のようなビット演算で設定できますが、初心者のうちは set_collision_mask_value(3, true) のように番号で指定するほうが読み間違いにくいです。

ただし、番号だけを大量に書くと後から読めなくなります。上の例のように const LAYER_ENEMY := 3 と名前を付けるか、インスペクタ中心で設定することをおすすめします。

Sponsored

反応しないときのチェックリスト

Layer/Maskの不具合は、原因が見た目だけでは分かりにくいです。「シグナルが来ない」「攻撃が当たらない」「アイテムを拾えない」ときは、順番に確認します。

Collision LayerとMaskが反応しないときのチェックリスト
確認項目見る場所よくある原因
Shapeがあるか子ノードCollisionShape2D がない、または disabled がオン
Layerが合っているか相手側相手が想定したLayerに所属していない
Maskが相手を見ているか検出する側Maskに相手Layerのチェックが入っていない
シグナルが接続されているかNodeタブ/コードbody_enteredarea_entered を間違えている
反応先のコードが正しいかスクリプトグループ名のtypo、メソッド名のtypo

特に多いのは、body_enteredarea_entered の取り違えです。

検出したい相手使うシグナル
CharacterBody2D, RigidBody2D, StaticBody2Dbody_entered
Area2Darea_entered

アイテムがプレイヤー本体を検出するなら body_entered です。攻撃HitBoxが敵HurtBoxを検出するなら、Area2D同士なので area_entered です。

また、Layer/Maskが正しくても、シグナルの中で is_in_group("player") の名前を間違えていると処理は止まります。物理設定だけでなく、最後に実行されるコードまで確認するのが大切です。

おまけ: 先に知っておくと良いこと

Layer/Maskは物理の入口、グループはゲームルールの確認

Layer/Maskは、物理エンジンに「この組み合わせだけ判定して」と伝える入口です。ここで候補を絞ると、不要な判定やシグナルを減らせます。

一方で、Layerだけでゲーム内の意味をすべて表現しようとすると、すぐにレイヤーが足りなくなります。たとえば敵にはスライム、ゴブリン、ボス、召喚物などがいますが、それらを全部別Layerにすると管理が重くなります。

この場合は、物理的には同じ EnemyHurtBox Layerにまとめ、スクリプト側で enemy, boss, flying_enemy のようなグループやプロパティを確認します。

func _on_area_entered(area: Area2D) -> void:
    if not area.is_in_group("enemy_hurtbox"):
        return

    # ボスだけはダメージ倍率を変える、というゲームルールはコード側で判断します。
    var final_damage := damage
    if area.is_in_group("boss"):
        final_damage *= 0.5

    area.apply_damage(final_damage)

Layer/Maskは「物理的に候補にするか」、グループは「ゲーム上どう扱うか」です。この分担にすると、レイヤー設計がシンプルになります。

レイヤーを細かくしすぎない

初心者のうちは、プレイヤー、敵、弾、アイテム、壁など、見える種類ごとにどんどんLayerを分けたくなります。しかし、Layerは増やすほど設定表も複雑になります。

判断基準は「物理的に別の相手を見たいか」です。

たとえば赤スライムと青スライムが同じようにプレイヤーの攻撃を受けるなら、同じ EnemyHurtBox で十分です。色や属性の違いは、グループやスクリプトのプロパティで扱います。

逆に、プレイヤーの攻撃と敵の攻撃は分ける価値があります。プレイヤーの攻撃がプレイヤーに当たってほしくない、敵の攻撃が敵に当たってほしくない、というように判定対象が違うからです。

迷ったら相互作用表を作る

Layer/Maskの設定で混乱したら、先に表を書きます。

何が反応する?      何に反応する?
PlayerHitBox  ->   EnemyHurtBox
EnemyHitBox   ->   PlayerHurtBox
ItemArea      ->   Player
EnemySight    ->   Player

この矢印の左側が、主にMaskを持つ側です。右側がLayerとして見られる側です。矢印を文章で説明できない設定は、あとで高確率でバグになります。

まとめ

Collision Layerは「自分は何者か」、Collision Maskは「何を見に行くか」です。

この2つを使うと、プレイヤー、敵、壁、攻撃判定、アイテム取得の関係をインスペクタ上で整理できます。特に攻撃判定では、本体、HitBox、HurtBoxを分けると、移動とダメージ処理が混ざりにくくなります。

反応しないときは、Shape、Layer、Mask、シグナル、グループ名の順に確認してください。Layer/Maskは難しい設定に見えますが、相互作用表を作ってから設定すれば、かなり安定して扱えるようになります。

さらに学ぶために