Introduzione
Godot 4 ha un sistema di navigazione robusto costruito su NavigationServer2D/3D, i nodi NavigationRegion e i nodi NavigationAgent. È un miglioramento significativo rispetto al sistema di Godot 3 — più flessibile, più performante e molto più facile da configurare per scenari complessi come ostacoli dinamici e più tipi di agente.
Panoramica rapida: NavigationRegion definisce dove gli agenti possono camminare. NavigationAgent gestisce il pathfinding per le singole entità. NavigationServer gestisce tutto dietro le quinte.
Concetti fondamentali
- NavigationRegion2D / NavigationRegion3D — Definisce un'area percorribile tramite un NavigationPolygon (2D) o un NavigationMesh (3D). Puoi avere più regioni; il server le unisce automaticamente.
- NavigationAgent2D / NavigationAgent3D — Da collegare a un CharacterBody per gestire il pathfinding. Calcola la posizione successiva del percorso, gestisce l'avoidance ed emette segnali quando la navigazione è terminata.
- NavigationServer2D / NavigationServer3D — Singleton che gestisce tutti i dati di navigazione. Raramente ci interagisci direttamente, ma si occupa degli aggiornamenti della mappa, delle query di percorso e delle connessioni tra regioni.
Configurare la navigazione 2D
Passo 1: aggiungere un NavigationRegion2D
- Aggiungi un nodo NavigationRegion2D alla tua scena.
- Nell'Inspector, crea una nuova risorsa NavigationPolygon.
- Disegna il poligono percorribile nell'editor 2D. Il poligono definisce dove gli agenti possono camminare.
Integrazione con la TileMap: un NavigationPolygon può essere generato (baked) dai dati della TileMap. Se le tue tile hanno poligoni di navigazione definiti nel TileSet, basta aggiungere un NavigationRegion2D ed eseguire il baking — genererà automaticamente l'area percorribile dal layout delle tue tile.
Aggiungere un NavigationAgent2D
Aggiungi un NavigationAgent2D come figlio del tuo CharacterBody2D. Ecco uno script di movimento completo:
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()
Importante: il NavigationServer ha bisogno di un frame di fisica per sincronizzarsi. Esegui sempre await get_tree().physics_frame prima di impostare la prima posizione di destinazione in _ready(), altrimenti l'agente potrebbe non trovare un percorso valido.
Proprietà chiave di NavigationAgent2D
-
target_position— Dove l'agente vuole andare -
path_desired_distance— Quanto l'agente deve essere vicino a ciascun punto del percorso per avanzare al successivo -
target_desired_distance— Quanto l'agente deve essere vicino alla destinazione perché la navigazione sia considerata terminata -
max_speed— Usato per i calcoli di avoidance (non limita il tuo codice di movimento)
Navigazione 3D
Il sistema di navigazione 3D funziona in modo identico al 2D, solo con tipi di nodo diversi:
NavigationRegion3D+NavigationMeshNavigationAgent3DNavigationServer3D
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()
Baking delle navigation mesh
In 3D di solito generi la navigation mesh dalla geometria del livello (baking) invece di disegnarla a mano.
Configurazione del baking
Proprietà chiave nella risorsa NavigationMesh:
- Agent Radius — Quanto gli agenti restano lontani dalle pareti. Valori più grandi creano percorsi più conservativi.
- Agent Height — Altezza minima del soffitto per le aree percorribili.
- Agent Max Climb — Altezza massima del gradino che gli agenti possono salire (es. scale).
- Agent Max Slope — Angolo massimo di pendenza percorribile in gradi.
# 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!")
Layer di navigazione
I layer di navigazione ti permettono di separare le aree percorribili per i diversi tipi di agente. Ad esempio, le unità di terra e le unità volanti possono avere navigation mesh differenti.
# 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
Una configurazione tipica dei layer:
- Layer 1 — Unità di terra (soldati, veicoli)
- Layer 2 — Unità volanti (droni, uccelli)
- Layer 3 — Unità grandi (solo corridoi ampi)
Avoidance
NavigationAgent dispone di un avoidance locale integrato per impedire che gli agenti si sovrappongano. Quando l'avoidance è attivo, l'agente calcola una velocità sicura che lo allontana dagli altri agenti.
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()
Come funziona: invece di usare la velocità direttamente, imposti nav_agent.velocity sulla velocità desiderata. L'agente calcola quindi una safe_velocity tramite il segnale velocity_computed che evita gli agenti vicini. Applichi questa velocità sicura nel callback.
Navigazione dinamica
Puoi modificare la navigation mesh a runtime per gestire porte, muri distruttibili o terreni che cambiano:
# 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
Nota sulle prestazioni: il baking di una navigation mesh 3D a runtime è oneroso. Per gli ostacoli dinamici in 3D, preferisci usare NavigationObstacle3D invece di rieseguire il baking. In 2D puoi commutare NavigationRegion2D.enabled a basso costo.
Pattern comuni
IA nemica che insegue il giocatore
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()
Rotte di pattuglia degli 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()
Click-to-move (top-down)
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()
Cambiamenti da Godot 3 a Godot 4
| Godot 3 | Godot 4 |
|---|---|
Nodo Navigation2D |
Rimosso. Usa NavigationRegion2D + NavigationAgent2D |
Navigation.get_simple_path() |
NavigationServer2D.map_get_path() |
| Path following manuale | Se ne occupa NavigationAgent.get_next_path_position() |
| Nessun avoidance integrato | NavigationAgent.avoidance_enabled |
| Nessun layer di navigazione | 32 layer di navigazione per mappa |
| Nessun ostacolo | NavigationObstacle2D/3D |