敵がプレイヤーを障害物ごしに追いかけてくる。警備兵が決まったルートを黙々と巡回する——こうした「賢い移動」は、ナビゲーションメッシュ(AI用の地図) の上を歩く NavigationAgent2D が実現します。
前回の記事で「歩ける地図 」をベイクしました。この記事は、 その地図の上を実際に移動させる 側の話です。目的地を指定するだけで、経路計算も障害物回避もエージェントが引き受けてくれるので、私たちは「どこへ向かうか」を決めるだけで済みます。追跡と巡回、2つの定番を作れるようにしましょう。
この記事でわかること
NavigationAgent2Dの 基本の処理サイクル(目標→経路→次の点→移動)get_next_path_position()が返すのは 「次の中継点」 という要点- 追跡AI と 巡回AI の実装
velocity_computedによる エージェント同士の回避(RVO)- 経路再計算の パフォーマンス最適化 と
AStar2Dとの違い
NavigationAgent2Dの基本サイクル
NavigationAgent2D は、移動させたいキャラ(CharacterBody2D)の 子ノード として置きます。使い方は、毎フレーム次のサイクルを回すだけです。

- 目標を決める :
target_positionに行き先の座標を入れる - 経路を計算 :ナビゲーションが自動で最短経路を求める
- 次の点を受け取る :
get_next_path_position()で経路上の次の中継点を取得 - そこへ移動 :その点へ向かって
move_and_slide()
経路探索や障害物回避の難しい部分は、すべてエージェントが内部で処理してくれます。私たちが書くのは「目標を決める」と「返ってきた点へ動く」だけです。
get_next_path_positionは「次の中継点」
初心者がつまずきやすいのが、get_next_path_position() の意味です。これは ゴールそのものではなく、経路上の「次に向かうべき中継点」 を返します。

だから、返ってきた座標へ まっすぐ向かって移動 させます。ゴール(target_position)に直接向かうと障害物にぶつかってしまいますが、中継点を1つずつたどれば、経路に沿って自然に回り込めます。
var next_point := navigation_agent.get_next_path_position()
var direction := global_position.direction_to(next_point) # 次の中継点への向き
velocity = direction * speed
move_and_slide()
追跡AI:動く目標を追う
まずは、敵がプレイヤーを追いかける追跡AIです。target_position に プレイヤーの現在地 を入れ続けるだけで、障害物を避けながら追ってきます。

ポイントは、 target_position を毎フレーム更新しない ことです。目標を更新するたびに経路が再計算されるため、毎フレームだと重くなります。Timer で0.2秒ごとくらいに間引きます。
extends CharacterBody2D
@export var speed: float = 250.0
@export var player: Node2D
@onready var navigation_agent: NavigationAgent2D = $NavigationAgent2D
@onready var repath_timer: Timer = $RepathTimer # 0.2秒周期でautostart
func _ready() -> void:
repath_timer.timeout.connect(_update_target)
func _update_target() -> void:
if is_instance_valid(player):
navigation_agent.target_position = player.global_position # 目標更新=経路再計算
func _physics_process(_delta: float) -> void:
if navigation_agent.is_navigation_finished(): # 到達したら止まる
return
var next_point := navigation_agent.get_next_path_position()
velocity = global_position.direction_to(next_point) * speed
move_and_slide()
is_navigation_finished() で「もう着いた?」を判定できるので、着いたら移動を止めます。経路の再計算は Timer に任せ、_physics_process は 移動だけ に集中させるのがきれいな形です。
巡回AI:決めた地点を順に回る
次は、決められたポイントを順番に回る巡回AIです。目標に着いたら次のポイントへ切り替える——is_navigation_finished() がここで活きます。
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 index := 0
func _ready() -> void:
navigation_agent.target_position = patrol_points[index]
func _physics_process(_delta: float) -> void:
if navigation_agent.is_navigation_finished():
# 次のポイントへ(末尾まで行ったら最初に戻る)
index = (index + 1) % patrol_points.size()
navigation_agent.target_position = patrol_points[index]
return
var next_point := navigation_agent.get_next_path_position()
velocity = global_position.direction_to(next_point) * speed
move_and_slide()
巡回では目標が ポイント到達時にだけ 変わるので、追跡のような間引きは不要です。「いつ追跡に切り替えるか(プレイヤーを見つけたら)」は ステートマシン と組み合わせると、巡回⇄追跡を自然に行き来する敵が作れます。
エージェント同士の回避(RVO)
敵を複数出すと、今度は 敵同士が重なって団子になる 問題が出ます。これを防ぐのが RVO(相互速度障害物法) による回避です。

使うには、エージェントの avoidance_enabled を有効にし、 速度を直接動かす代わりに set_velocity() へ「進みたい速度」を渡します。すると、他のエージェントを避けた 安全な速度 が velocity_computed シグナルで返ってくるので、それで move_and_slide() します。
func _physics_process(_delta: float) -> void:
if navigation_agent.is_navigation_finished():
return
var next_point := navigation_agent.get_next_path_position()
var desired := global_position.direction_to(next_point) * speed
navigation_agent.set_velocity(desired) # 進みたい速度を渡す(そのまま動かさない)
# 回避を考慮した安全な速度が返ってくる
func _on_navigation_agent_2d_velocity_computed(safe_velocity: Vector2) -> void:
velocity = safe_velocity
move_and_slide()
avoidance_enabled がオンのときは、 必ず velocity_computed の中で move_and_slide() する のが肝心です。_physics_process でも直接動かすと二重移動になり、回避が効きません。
パフォーマンスとAStar2Dとの違い
大量のエージェントを動かすときは、 経路の再計算頻度 が最大のボトルネックです。

- 再計算を間引く :
target_positionの更新はTimerで0.2秒ごとなどに。重要度の低い敵はさらに間隔を空ける - 画面外は止める :遠くや画面外のAIは
set_physics_process(false)で処理を止める NavigationObstacle2Dは最小限に :動的障害物は便利ですが高コストなので数を絞る
AStar2Dとの使い分け
グリッドベースのゲームでは、より低レベルな AStar2D が向く場合もあります。
NavigationAgent2D | AStar2D | |
|---|---|---|
| 抽象度 | 高レベル(使いやすい) | 低レベル(柔軟) |
| セットアップ | メッシュをベイクするだけ | ポイントと接続を手動定義 |
| 障害物回避 | 自動(動的障害物・RVOも) | 自前で実装 |
| 向くケース | キャラの自由な空間移動 | マス目移動・ターン制ストラテジー |
マップ上を リアルタイムに自由移動 する一般的な敵AIなら NavigationAgent2D、盤面がマス目で 経路をきっちり制御 したいなら AStar2D、と覚えておけば十分です。
おまけ:先に知っておくと良いこと
- まず地図を確認 :エージェントが動かない9割は、地図(メッシュ)側の問題です。NavigationRegion2D が範囲を覆ってベイクできているかを最初に疑います。
- 止まらない・震えるときは許容距離 :目的地でピタッと止まらないなら
target_desired_distanceを少し大きく(例: 10.0)します。 - 速度を滑らかに :
velocity = velocity.lerp(desired, 0.1)のように補間すると、キビキビしすぎない自然な加減速になります。 - 判断はAI側に分ける :「巡回するか・追うか・逃げるか」の判断は ステートマシン やビヘイビアツリーの担当です。ナビは「決まった目標への移動」に徹させると、設計がすっきりします。
まとめ
- 基本サイクル :
target_positionを決め、get_next_path_position()の中継点へmove_and_slide() - 中継点を1つずつ たどるから、障害物を自然に回り込める
- 追跡 は
target_positionをTimerで間引き更新、巡回 は到達ごとに次ポイントへ - RVO は
avoidance_enabled+set_velocity()+velocity_computedでエージェント同士の重なりを防ぐ - 最適化 は再計算の間引きと画面外の停止が基本
まずは ベイクした地図 の上に敵を1体置き、プレイヤーを target_position にして追跡させてみてください。障害物を回り込んで追ってくる姿が見えたら、ナビゲーションの土台は完成です。あとは巡回やRVO、状態管理を足していけば、ぐっとゲームらしい敵AIになります。
さらに学ぶために
- NavigationRegion2Dとナビゲーションメッシュ ——このエージェントが歩く「地図」の作り方(前提記事)
- ステートマシンで管理するAIとプレイヤーの状態 ——巡回⇄追跡⇄逃走の判断を組む
- Collision LayersとMasksでオブジェクトを整理する ——プレイヤー検知や障害物のレイヤー設定
- Godot公式ドキュメント:Using NavigationAgents ——エージェントの一次情報