【Godot】NavigationAgent2Dによるパスファインディング - 敵AIの追跡と巡回

作成: 2025-12-08最終更新: 2025-12-16

Godot EngineのNavigationAgent2Dを使用して、障害物を回避しながら敵AIの追跡・巡回動作を実装する方法を、実践的なコードとパフォーマンス最適化のポイントとともに解説します。

導入: なぜゲームAIにパスファインディングが必要なのか

ゲーム開発において、敵キャラクターやNPCの 移動 は、プレイヤー体験の質を大きく左右する要素です。壁や障害物、入り組んだマップが存在する場合、AIは目的地までの 最適な経路 を見つけ出す必要があります。この「最適な経路を見つけ出す」技術こそが パスファインディング です。

Godot Engineでは、この複雑なパスファインディング処理を簡単かつ効率的に実現するために、 NavigationAgent2D ノードを提供しています。


NavigationAgent2Dの仕組みと基本セットアップ

Godotのナビゲーションシステムは、主に3つの要素で構成されています。

要素ノード名役割
ナビゲーションマップNavigationServer2Dシーン全体の移動可能領域データを管理するバックエンドサーバー。
移動可能領域NavigationRegion2DAIが移動できるマップ上の領域(ナビゲーションメッシュ)を定義する。
経路探索エージェントNavigationAgent2D経路計算を要求し、親ノード(AIキャラクター)の移動を制御する。
動的障害物NavigationObstacle2D移動するドアや他のキャラクターなど、動的な障害物を定義し、エージェントに回避させる。

セットアップの基本

NavigationAgent2D を機能させるには、まずマップ上に 移動可能な領域 を定義する必要があります。

  1. NavigationRegion2Dの配置: シーンツリーに NavigationRegion2D ノードを追加します。
  2. NavigationPolygonの作成: NavigationRegion2D のインスペクターで、新しい NavigationPolygon リソースを作成し、マップの移動可能な部分をカバーするように編集します。
  3. AIキャラクターの準備: 敵AIのルートノード(例: CharacterBody2D)の子として NavigationAgent2D ノードを追加します。

実践例1: 敵AIによるプレイヤー追跡

敵AIがプレイヤーを追跡するロジックは、パスファインディングの最も基本的なユースケースです。NavigationAgent2D を使えば、障害物を避けながら最短経路で追いかけるAIが簡単に作れます。

# EnemyAI.gd (CharacterBody2Dにアタッチ)
extends CharacterBody2D

@export var speed: float = 250.0

# プレイヤーノードへの参照(インスペクターから設定)
@export var player: Node2D

@onready var navigation_agent: NavigationAgent2D = $NavigationAgent2D

func _ready() -> void:
    # max_speedはRVO(相互速度障害物)回避用のパラメータです
    # 実際の移動速度はvelocityの計算で使用するspeed変数で制御します
    navigation_agent.max_speed = speed
    # プレイヤーがいない場合は何もしない
    if not is_instance_valid(player):
        set_physics_process(false)

func _physics_process(delta: float) -> void:
    # 目的地に到達したら、それ以上何もしない
    if navigation_agent.is_navigation_finished():
        velocity = Vector2.ZERO
        move_and_slide()
        return

    # プレイヤーの位置を目標に設定
    # パフォーマンスのため、毎フレームではなくタイマーで更新するのが望ましい(後述)
    navigation_agent.target_position = player.global_position

    # 次に進むべきポイントを取得
    var next_path_position: Vector2 = navigation_agent.get_next_path_position()
    # そのポイントへの方向を計算
    var direction: Vector2 = global_position.direction_to(next_path_position)

    # 計算された方向に沿って移動
    velocity = direction * speed
    move_and_slide()

このコードの核心は、navigation_agent.target_position にプレイヤーの座標を設定し、navigation_agent.get_next_path_position() で返された座標に向かって move_and_slide() するだけ、というシンプルさにあります。経路計算や障害物回避の複雑なロジックは、すべて NavigationAgent2D が内部で処理してくれます。


実践例2: 決められたルートを巡回する警備AI

敵AIに複数のポイントを順番に巡回させるパトロール動作も、 NavigationAgent2D を使えば容易に実装できます。目標地点に到達したかどうかを判定する is_navigation_finished() が重要な役割を果たします。

# PatrolAI.gd (CharacterBody2Dにアタッチ)
extends CharacterBody2D

@export var speed: float = 150.0
# 巡回ポイントの配列
@export var patrol_points: Array[Vector2] = [
    Vector2(100, 100), Vector2(800, 100), Vector2(800, 500), Vector2(100, 500)
]

@onready var navigation_agent: NavigationAgent2D = $NavigationAgent2D

var current_point_index: int = 0

func _ready() -> void:
    navigation_agent.max_speed = speed
    # 最初の巡回ポイントを目標に設定
    _set_next_patrol_point()

func _physics_process(delta: float) -> void:
    # is_navigation_finished()は目標に到達したかを判定する信頼性の高い方法
    if navigation_agent.is_navigation_finished():
        # 次のポイントへ
        current_point_index = (current_point_index + 1) % patrol_points.size()
        _set_next_patrol_point()

    # 追跡ロジックと同様に、次のポイントに向かって移動
    var next_path_position: Vector2 = navigation_agent.get_next_path_position()
    var direction: Vector2 = global_position.direction_to(next_path_position)

    velocity = direction * speed
    move_and_slide()

func _set_next_patrol_point() -> void:
    if patrol_points.is_empty():
        return
    navigation_agent.target_position = patrol_points[current_point_index]

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

NavigationAgent2D は強力ですが、使い方を誤ると予期せぬ挙動やパフォーマンス低下を招きます。

よくある間違いベストプラクティス
毎フレームtarget_positionを更新Timerノードを使い、0.1〜0.3秒ごとに目標位置を更新します。または、目標との距離が一定以上離れた場合にのみ更新するロジックを組み込みます。
is_navigation_finished()のみで到達判定navigation_agent.distance_to_target()も併用し、目標地点から一定の許容範囲内に入ったら到達したと見なすことで、より確実な判定ができます。
velocityを直接設定するvelocity = velocity.lerp(direction * speed, weight) を使い、現在の速度から目標速度へ滑らかに補間することで、自然な加減速を表現できます。
複雑な形状を手動で作成TileMapノードのレイヤー機能を使い、特定のタイルから自動的にナビゲーションメッシュを生成するスクリプトを作成します。

パフォーマンスに関する注意点と代替パターン

パフォーマンス最適化

  • 経路再計算の頻度: target_positionの更新は最大のボトルネックになり得ます。AIの重要度や数に応じて、更新頻度を適切に設定することが重要です。
  • 動的障害物(NavigationObstacle2D): 動く床やドアなどにNavigationObstacle2Dを使うと便利ですが、高コストな処理です。使用は必要最小限に留めましょう。
  • エージェントの数: 遠くにいるAIや画面外のAIは、処理を簡略化または一時停止する(set_physics_process(false))などの最適化が効果的です。

代替パターン: AStar2Dとの比較

特徴NavigationAgent2DAStar2D
抽象度高レベル(使いやすい)低レベル(柔軟性が高い)
セットアップNavigationRegion2Dをベイクするだけ手動でポイントと接続を定義する必要がある
障害物回避自動(動的障害物にも対応)自前で実装する必要がある
ユースケースキャラクターの空間移動グリッドベースのゲーム、ターン制ストラテジー

キャラクターがマップ内をリアルタイムに移動するような一般的なケースでは、NavigationAgent2Dが便利で効率的です。


まとめ

Godot Engineの NavigationAgent2D は、2DゲームにおけるAIの移動ロジックを劇的に簡素化する強力なツールです。

  • NavigationRegion2D で移動可能領域を定義し、
  • NavigationAgent2D に目標位置を設定し、
  • get_next_path_position() で得られた次のポイントに向かってキャラクターを移動させる

というシンプルな手順で、障害物を賢く回避し、プレイヤーを追跡したり、決められたルートを巡回したりする、 実践的で自然なAI を即座にゲームに組み込むことができます。