【Godot】グループによる柔軟なオブジェクト判定

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

Godotのグループ機能は、ノードにタグを付けて役割で管理する仕組みです。is_in_groupでの判定、call_groupでの一斉命令、パフォーマンス注意点、メタデータ・class_name・ダックタイピングとの使い分けまで図解で解説します。

「画面上の敵全員を一斉に怯ませたい」「プレイヤーの攻撃が当たったのが敵なのか、壊せる箱なのか、アイテムなのかを見分けたい」——ゲームを作っていると、こうした「役割でまとめて扱いたい」場面が次々に出てきます。

これをノードの親子関係や if body.name == "Slime" のような名前判定でこなそうとすると、敵を1種類増やすたびにコードを直す羽目になり、すぐに破綻します。そこで役立つのがGodotの グループ(Groups) 機能です。ノードに 役割のタグ を貼っておけば、種類やクラスを気にせず「タグでまとめて」判定・命令できます。

グループのイメージ。バラバラの種類のノードに、enemiesやplayersといった役割のタグが貼られ、タグ単位でまとめて扱えるようになる様子

この記事でわかること

  • グループ =シーン階層から独立した「役割のタグ付け」システム
  • add_to_group() での登録と、is_in_group() での 柔軟な判定
  • call_group() による グループへの一斉命令
  • メタデータ・class_name・ダックタイピングとの 使い分け

Sponsored

グループとは?役割の「タグ付け」システム

グループは、シーンツリーの階層やノードの種類とは 無関係に、ノードへ仮想的な 「タグ」 を貼るシステムです。CharacterBody2D に「enemies」、Area2D に「collectible_items」——といった具合に、クラスが違っても同じタグでまとめられます。

グループがシーン階層を横断することを示す図。ツリー上ではバラバラの階層にいるスライム・ゴブリン・コウモリが、enemiesという1つのグループタグで横断的にまとめられている

ポイントは、この分類が ツリー構造を横断する ことです。ツリーのどこに置かれていても、種類が何であっても、「enemies」タグさえ貼れば同じ仲間として扱えます。グループの利点は3つです。

  • 役割で分類できる :親子関係に縛られず、「敵」「拾えるもの」といった意味のまとまりで括れる
  • 一括操作できる :「enemies」全員を取得してまとめて命令、が簡単に書ける
  • 疎結合になる :ノード同士が直接参照し合わなくても、タグという共通の目印で連携できる

1つのノードは 複数のグループ に入れます。敵を「enemies」かつ「rangers(遠距離)」に入れておけば、「全敵」にも「遠距離の敵だけ」にも絞り込めます。

グループに追加する2つの方法

グループへの追加は、 エディタコード の2通りです。配置済みの静的なノードはエディタで、実行中に生成されるノードはコードで、と使い分けます。

グループに追加する2つの方法の図。左は静的なノード向けにエディタのノードタブ・グループから名前を入力して追加、右は動的なノード向けにadd_to_groupをコードで呼ぶ

エディタで追加(静的なノード向け) :シーンツリーでノードを選び、インスペクタ隣の「ノード」タブ →「グループ」を開き、グループ名(例: enemies)を入力して「追加」を押します。

コードで追加(動的なノード向け) :弾やエフェクトのように実行中に生成されるノードは、add_to_group() で登録します。

# bullet.gd
func _ready() -> void:
    add_to_group("player_bullets")   # 生成時に自分をグループへ登録

func _on_hit_target() -> void:
    remove_from_group("player_bullets")   # 不要になったら抜ける(任意)
    queue_free()
Sponsored

is_in_groupで「相手が何者か」を判定する

グループがもっとも輝くのが、 衝突相手の判定 です。is_in_group() を使うと、「ぶつかった相手が敵か、壊せる箱か」を 具体的なクラスを知らずに 見分けられます。

is_in_groupによる衝突判定の分岐図。PlayerAttackAreaにbodyが入ると、enemiesグループならtake_damage、destructiblesグループならdestroyと、相手のタグに応じて処理が分かれる
# player_attack_area.gd(Area2D:プレイヤーの剣の当たり判定)
func _on_body_entered(body: Node) -> void:
    # 相手が "enemies" タグを持ち、take_damage を実装しているか
    if body.is_in_group("enemies") and body.has_method("take_damage"):
        body.take_damage(attack_power)
    # 壊せるオブジェクトなら壊す
    elif body.is_in_group("destructible_objects") and body.has_method("destroy"):
        body.destroy()

このコードの美点は、PlayerAttackArea が攻撃対象の具体的なクラス(SlimeGoblin)を 一切知らない ことです。新しい敵を追加しても、このスクリプトは無修正でよく、 新しいノードを「enemies」グループに入れるだけ で攻撃が通るようになります。is_in_group()(タグ確認)と has_method()(振る舞い確認)を組み合わせると、より安全です。

実践:画面全体の敵に一斉命令する

ボムアイテムで全画面の敵を怯ませる、警報で全警備員を警戒させる、ターン制で敵全体に状態異常を配る——「特定の役割のノード全員に、同じ命令を一斉に送りたい」場面は、ジャンルを問わず頻出します。get_tree().call_group() を使えば、 相手が何体いてどこにいるかを知らなくても 、グループ全員のメソッドを一度に呼べます。

call_groupによる一斉命令の図。AlarmSystemがcall_groupでguardsグループを指定すると、シーン内に散らばった複数のguardすべてのenter_alert_modeが一斉に呼ばれ、全員が警戒モードに移行する

警報システムを例に組んでみましょう。命令を送る側は、call_group() を1回呼ぶだけです。

# alarm_system.gd
func _on_alarm_triggered() -> void:
    # "guards" グループ全員の enter_alert_mode を、引数つきで一斉に呼ぶ
    get_tree().call_group("guards", "enter_alert_mode", get_player_last_position())

命令を受ける側(警備員)は、グループに入っておき、呼ばれるメソッドを用意しておくだけです。

# guard.gd(CharacterBody2D)
func _ready() -> void:
    add_to_group("guards")   # 生成時にguardsへ登録

func enter_alert_mode(target_position: Vector2) -> void:
    state = State.ALERT              # 状態を警戒へ
    nav_agent.target_position = target_position   # 最後に見た位置へ向かう

ポイントは2つです。

  • 送る側は「何体いるか」を知らなくてよいAlarmSystem はシーン内の警備員の数も位置も気にしません。増えても減っても、call_group() の1行は変わらないので、 敵の増減に強い 設計になります。
  • メソッドが無いノードは黙って無視されるcall_group() は、対象がそのメソッドを持たなくても エラーにならず何もしません 。逆に言えば呼び忘れに気づきにくいので、グループ内のノードが確実に該当メソッドを持つよう揃えるか、has_method() で確認します。

警備員一人ひとりの「警戒→追跡」の賢い動きは ステートマシンで管理するAIとプレイヤーの状態、目標地点への移動は NavigationAgent2Dによるパスファインディング の領域です。グループは「全員に号令をかける」役に徹し、個々の頭脳は各ノードに任せるときれいに分業できます。

Sponsored

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

よくある間違いベストプラクティス
_process 内で get_nodes_in_group() を呼ぶ_ready() でリストをキャッシュするか、call_group()・シグナルを使う。毎フレームのシーン全探索は高コスト
あらゆる判定をグループで済ませる役割は動的な分類向き。静的な型判定は class_name、値の付与はメタデータ、と使い分ける
グループ名を文字列で直書きするconst ENEMY_GROUP := "enemies" のように定数化し、タイプミスや変更漏れを防ぐ

特に グループ名の直書き は地味に効いてきます。"enemies" を何十箇所にも書くと、綴りミス1つでその判定だけ静かに動かなくなります。定数やAutoloadにまとめておけば、名前の変更も1箇所で済みます。

他の手法との使い分け

グループは万能ではありません。目的によっては、別の仕組みの方がシンプルで速いこともあります。

グループと代替手法の使い分けの図。グループは役割でまとめて検索、メタデータは静的な値の付与、class_nameは型で判定、has_methodは振る舞いで判定、と4つの得意分野を並べる
手法得意なこと使いどころ
グループ役割でまとめて検索・一括命令「全敵」「拾えるもの」など動的な役割
メタデータ set_meta/get_metaノードに静的な値を付与item_id など個体ごとの固定値
class_name型で厳密に判定(is Enemy特定クラスのインスタンスを扱う
ダックタイピング has_method型を知らず振る舞いで分岐「take_damage を持つなら」で処理する

これらは競合ではなく 相補的 です。実際、先ほどの当たり判定でも「グループで役割を確認しつつ has_method() で振る舞いも確認」と組み合わせていました。役割はグループ、型は class_name、能力は has_method、値はメタデータ——と、それぞれの得意分野で使い分けるのがきれいな設計です。

なお、物理的な「誰と誰がぶつかるか」の制御はグループではなく Collision LayersとMasks の担当です。グループは衝突「後」のロジック判定、レイヤーは衝突「そのもの」のフィルタ、と役割が分かれています。

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

  • シグナルとの相性 :グループは「こちらから全員へ号令」に向きます。逆に「何かが起きたことを各所へ知らせる」なら シグナル が適します。号令はグループ、通知はシグナル、と覚えると迷いません。
  • グループ内の順序は保証されないget_nodes_in_group() の並び順に依存した処理は書かないのが安全です。順序が要るなら自前で並べ替えます。
  • call_group にはフラグ版もあるcall_group_flags() を使うと、呼び出しを遅延させる(そのフレームの最後にまとめて実行)などの制御ができます。大量ノードへの一斉命令で役立つ場面があります。

まとめ

  • グループ はシーン階層から独立した「役割のタグ付け」。クラスが違っても同じタグでまとめられる
  • 追加はエディタ(静的)と add_to_group()(動的)の2通り
  • is_in_group()相手の役割を判定 し、具体的なクラスを知らずに処理を分岐できる
  • get_tree().call_group()グループ全員に一斉命令 。送る側は数も位置も知らなくてよい
  • get_nodes_in_group() の毎フレーム呼び出しは避け、キャッシュや call_group() を使う
  • メタデータ・class_namehas_method適材適所 で組み合わせる

まずは敵を「enemies」グループに入れ、プレイヤーの攻撃で is_in_group("enemies") を判定するところから試してみてください。「クラスを増やしてもコードが増えない」感覚が、グループの本当のうれしさです。

さらに学ぶために