はじめに

Godot 4にはNavigationServer2D/3D、NavigationRegionノード、NavigationAgentノードを基盤とした堅牢なナビゲーションシステムがあります。Godot 3のシステムから大幅に改善され、動的障害物や複数エージェントタイプなどの複雑なシナリオの設定が、より柔軟で高性能かつ簡単になっています。

概要: NavigationRegionはエージェントが歩ける場所を定義します。NavigationAgentは個々のエンティティのパスファインディングを処理します。NavigationServerがバックグラウンドですべてを管理します。

基本概念

  • NavigationRegion2D / NavigationRegion3D — NavigationPolygon(2D)またはNavigationMesh(3D)を使って歩行可能エリアを定義します。複数のリージョンを持てます。サーバーが自動的に結合します。
  • NavigationAgent2D / NavigationAgent3D — CharacterBodyにアタッチしてパスファインディングを処理します。次のパス位置を計算し、回避を処理し、ナビゲーション完了時にシグナルを発信します。
  • NavigationServer2D / NavigationServer3D — すべてのナビゲーションデータを管理するシングルトン。直接操作することは稀ですが、マップ更新、パスクエリ、リージョン接続を処理します。

2Dナビゲーションのセットアップ

ステップ1:NavigationRegion2Dの追加

  1. シーンにNavigationRegion2Dノードを追加します。
  2. インスペクターで新しいNavigationPolygonリソースを作成します。
  3. 2Dエディタで歩行可能なポリゴンを描画します。ポリゴンはエージェントが歩ける場所を定義します。

TileMap連携: NavigationPolygonはTileMapデータからベイクできます。タイルにTileSetでナビゲーションポリゴンが定義されていれば、NavigationRegion2Dを追加してベイクするだけで、タイルレイアウトから自動的に歩行可能エリアが生成されます。

NavigationAgent2Dの追加

CharacterBody2Dの子としてNavigationAgent2Dを追加します。完全な移動スクリプトは以下の通り:

extends CharacterBody2D

@export var speed: float = 200.0
@onready var nav_agent: NavigationAgent2D = $NavigationAgent2D

func _ready() -> void:
    # Wait for the navigation map to be ready
    await get_tree().physics_frame

    nav_agent.path_desired_distance = 4.0
    nav_agent.target_desired_distance = 4.0

func set_target(target_pos: Vector2) -> void:
    nav_agent.target_position = target_pos

func _physics_process(delta: float) -> void:
    if nav_agent.is_navigation_finished():
        return

    var next_pos := nav_agent.get_next_path_position()
    var direction := global_position.direction_to(next_pos)
    velocity = direction * speed
    move_and_slide()

重要: NavigationServerは同期に1物理フレームが必要です。_ready()で最初のターゲット位置を設定する前に、必ずawait get_tree().physics_frameを実行してください。そうしないとエージェントが有効なパスを見つけられない場合があります。

NavigationAgent2Dの主要プロパティ

  • target_position — エージェントの目的地
  • path_desired_distance — 次のパスポイントに進むために必要な各パスポイントへの近接距離
  • target_desired_distance — ナビゲーション完了と判定するためのターゲットへの近接距離
  • max_speed — 回避計算に使用(移動コードの制限はしない)

3Dナビゲーション

3Dナビゲーションシステムは2Dと同様に動作します。ノードタイプが異なるだけです:

  • NavigationRegion3D + NavigationMesh
  • NavigationAgent3D
  • NavigationServer3D
extends CharacterBody3D

@export var speed: float = 5.0
@onready var nav_agent: NavigationAgent3D = $NavigationAgent3D

func _ready() -> void:
    await get_tree().physics_frame
    nav_agent.path_desired_distance = 0.5
    nav_agent.target_desired_distance = 0.5

func set_target(target_pos: Vector3) -> void:
    nav_agent.target_position = target_pos

func _physics_process(delta: float) -> void:
    if nav_agent.is_navigation_finished():
        return

    var next_pos := nav_agent.get_next_path_position()
    var direction := global_position.direction_to(next_pos)
    velocity = direction * speed
    velocity.y -= 9.8 * delta  # Apply gravity
    move_and_slide()

ナビゲーションメッシュのベイク

3Dでは通常、手動で描画するのではなく、レベルジオメトリからナビゲーションメッシュをベイクします。

ベイク設定

NavigationMeshリソースの主要プロパティ:

  • Agent Radius — エージェントが壁からどれだけ離れるか。値が大きいほど保守的なパスになります。
  • Agent Height — 歩行可能エリアの最小天井高。
  • Agent Max Climb — エージェントが歩いて登れる最大段差(例:階段)。
  • Agent Max Slope — 歩行可能な最大傾斜角度(度)。
# Bake navigation mesh at runtime
var nav_region: NavigationRegion3D = $NavigationRegion3D
nav_region.bake_navigation_mesh()

# Wait for baking to complete
await nav_region.bake_finished
print("Navigation mesh baked!")

ナビゲーションレイヤー

ナビゲーションレイヤーにより、エージェントタイプごとに歩行可能エリアを分離できます。例えば、地上ユニットと飛行ユニットに異なるナビゲーションメッシュを設定できます。

# Set navigation layers on the agent
nav_agent.set_navigation_layer_value(1, true)   # Ground layer
nav_agent.set_navigation_layer_value(2, false)  # Not flying layer

# Set navigation layers on the region
nav_region.set_navigation_layer_value(1, true)   # This region is for ground
nav_region.set_navigation_layer_value(2, false)  # Not for flying

一般的なレイヤー設定:

  • Layer 1 — 地上ユニット(兵士、車両)
  • Layer 2 — 飛行ユニット(ドローン、鳥)
  • Layer 3 — 大型ユニット(広い通路のみ)

回避(Avoidance)

NavigationAgentにはエージェントの重なりを防ぐためのローカル回避機能が組み込まれています。回避が有効な場合、エージェントは他のエージェントを避ける安全な速度を計算します。

extends CharacterBody2D

@export var speed: float = 200.0
@onready var nav_agent: NavigationAgent2D = $NavigationAgent2D

func _ready() -> void:
    await get_tree().physics_frame
    nav_agent.avoidance_enabled = true
    nav_agent.radius = 20.0
    nav_agent.max_speed = speed
    nav_agent.velocity_computed.connect(_on_velocity_computed)

func set_target(target_pos: Vector2) -> void:
    nav_agent.target_position = target_pos

func _physics_process(delta: float) -> void:
    if nav_agent.is_navigation_finished():
        return

    var next_pos := nav_agent.get_next_path_position()
    var direction := global_position.direction_to(next_pos)
    # Set the desired velocity — the agent will compute a safe one
    nav_agent.velocity = direction * speed

func _on_velocity_computed(safe_velocity: Vector2) -> void:
    velocity = safe_velocity
    move_and_slide()

仕組み: velocityを直接使う代わりに、nav_agent.velocityに希望の速度を設定します。エージェントがvelocity_computedシグナルを通じて、近くのエージェントを避けるsafe_velocityを計算します。コールバックでこの安全な速度を適用します。

動的ナビゲーション

ドア、破壊可能な壁、変化する地形に対応するため、ランタイムでナビゲーションメッシュを変更できます:

# Enable/disable a navigation region (e.g., open/close a door)
var door_region: NavigationRegion2D = $DoorNavigationRegion
door_region.enabled = false  # Block this path
door_region.enabled = true   # Open this path

# Add a navigation obstacle (blocks agent paths)
var obstacle := NavigationObstacle2D.new()
obstacle.radius = 30.0
add_child(obstacle)

# Re-bake after level changes (3D)
nav_region.bake_navigation_mesh()
await nav_region.bake_finished

パフォーマンス注意: 3Dナビゲーションメッシュのランタイムベイクはコストが高いです。3Dの動的障害物には再ベイクの代わりにNavigationObstacle3Dを使用してください。2DではNavigationRegion2D.enabledの切り替えは低コストです。

よくあるパターン

プレイヤーを追跡する敵AI

extends CharacterBody2D

@export var speed: float = 150.0
@export var chase_range: float = 300.0
@onready var nav_agent: NavigationAgent2D = $NavigationAgent2D
var player: Node2D

func _ready() -> void:
    await get_tree().physics_frame
    player = get_tree().get_first_node_in_group("player")

func _physics_process(delta: float) -> void:
    if not player:
        return

    var distance := global_position.distance_to(player.global_position)
    if distance > chase_range:
        return  # Too far, don't chase

    # Update target every frame (or throttle to every N frames)
    nav_agent.target_position = player.global_position

    if nav_agent.is_navigation_finished():
        return

    var next_pos := nav_agent.get_next_path_position()
    var direction := global_position.direction_to(next_pos)
    velocity = direction * speed
    move_and_slide()

NPCの巡回ルート

extends CharacterBody2D

@export var speed: float = 100.0
@export var patrol_points: Array[Vector2] = []
@onready var nav_agent: NavigationAgent2D = $NavigationAgent2D
var current_patrol_index: int = 0

func _ready() -> void:
    await get_tree().physics_frame
    if patrol_points.size() > 0:
        nav_agent.target_position = patrol_points[0]

func _physics_process(delta: float) -> void:
    if patrol_points.size() == 0:
        return

    if nav_agent.is_navigation_finished():
        # Move to next patrol point
        current_patrol_index = (current_patrol_index + 1) % patrol_points.size()
        nav_agent.target_position = patrol_points[current_patrol_index]
        return

    var next_pos := nav_agent.get_next_path_position()
    var direction := global_position.direction_to(next_pos)
    velocity = direction * speed
    move_and_slide()

クリック移動(トップダウン)

extends CharacterBody2D

@export var speed: float = 200.0
@onready var nav_agent: NavigationAgent2D = $NavigationAgent2D

func _ready() -> void:
    await get_tree().physics_frame

func _unhandled_input(event: InputEvent) -> void:
    if event is InputEventMouseButton and event.pressed:
        nav_agent.target_position = get_global_mouse_position()

func _physics_process(delta: float) -> void:
    if nav_agent.is_navigation_finished():
        return

    var next_pos := nav_agent.get_next_path_position()
    var direction := global_position.direction_to(next_pos)
    velocity = direction * speed
    move_and_slide()

Godot 3からGodot 4への変更点

Godot 3 Godot 4
Navigation2D node 廃止。NavigationRegion2D + NavigationAgent2Dを使用
Navigation.get_simple_path() NavigationServer2D.map_get_path()
手動パス追跡 NavigationAgent.get_next_path_position()が処理
回避機能なし NavigationAgent.avoidance_enabled
ナビゲーションレイヤーなし マップごとに32ナビゲーションレイヤー
障害物なし NavigationObstacle2D/3D