Introduction
Godot 4 dispose d'un système de navigation robuste bâti sur NavigationServer2D/3D, les nœuds NavigationRegion et les nœuds NavigationAgent. C'est une nette amélioration par rapport au système de Godot 3 — plus flexible, plus performant et bien plus facile à configurer pour des scénarios complexes comme les obstacles dynamiques et les multiples types d'agents.
Aperçu rapide : NavigationRegion définit où les agents peuvent se déplacer. NavigationAgent gère la recherche de chemin pour chaque entité. NavigationServer orchestre tout en arrière-plan.
Concepts de base
- NavigationRegion2D / NavigationRegion3D — définit une zone traversable avec un NavigationPolygon (2D) ou un NavigationMesh (3D). Tu peux avoir plusieurs régions ; le serveur les fusionne automatiquement.
- NavigationAgent2D / NavigationAgent3D — à attacher à un CharacterBody pour gérer la recherche de chemin. Il calcule la prochaine position du chemin, gère l'évitement et émet des signaux lorsque la navigation est terminée.
- NavigationServer2D / NavigationServer3D — singleton qui gère toutes les données de navigation. Tu interagis rarement directement avec lui, mais il prend en charge les mises à jour de carte, les requêtes de chemin et les connexions entre régions.
Configurer la navigation 2D
Étape 1 : ajouter un NavigationRegion2D
- Ajoute un nœud NavigationRegion2D à ta scène.
- Dans l'inspecteur, crée une nouvelle ressource NavigationPolygon.
- Dessine le polygone traversable dans l'éditeur 2D. Le polygone définit où les agents peuvent se déplacer.
Intégration TileMap : un NavigationPolygon peut être généré (baked) à partir des données de TileMap. Si tes tuiles ont des polygones de navigation définis dans le TileSet, ajoute simplement un NavigationRegion2D et lance le baking — il génère automatiquement la zone traversable à partir de la disposition de tes tuiles.
Ajouter un NavigationAgent2D
Ajoute un NavigationAgent2D comme enfant de ton CharacterBody2D. Voici un script de déplacement complet :
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()
Important : le NavigationServer a besoin d'une frame de physique pour se synchroniser. Exécute toujours await get_tree().physics_frame avant de définir la première position cible dans _ready(), sinon l'agent risque de ne pas trouver de chemin valide.
Propriétés clés de NavigationAgent2D
-
target_position— où l'agent souhaite aller -
path_desired_distance— à quelle distance l'agent doit être de chaque point du chemin pour passer au suivant -
target_desired_distance— à quelle distance l'agent doit être de la cible pour que la navigation soit considérée comme terminée -
max_speed— utilisée pour les calculs d'évitement (ne limite pas ton code de déplacement)
Navigation 3D
Le système de navigation 3D fonctionne de façon identique au système 2D, mais avec des types de nœuds différents :
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 des meshes de navigation
En 3D, tu génères habituellement le mesh de navigation à partir de la géométrie de ton niveau plutôt que de le dessiner à la main.
Configuration du baking
Propriétés clés de la ressource NavigationMesh :
- Agent Radius — à quelle distance les agents restent des murs. Des valeurs plus grandes produisent des chemins plus prudents.
- Agent Height — hauteur de plafond minimale pour les zones traversables.
- Agent Max Climb — hauteur de marche maximale que les agents peuvent gravir (par exemple des escaliers).
- Agent Max Slope — angle de pente maximal traversable, en degrés.
# 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!")
Calques de navigation
Les calques de navigation te permettent de séparer les zones traversables pour différents types d'agents. Par exemple, les unités au sol et les unités volantes peuvent avoir des meshes de navigation distincts.
# 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
Une configuration de calques typique :
- Layer 1 — unités au sol (soldats, véhicules)
- Layer 2 — unités volantes (drones, oiseaux)
- Layer 3 — grandes unités (couloirs larges uniquement)
Évitement (Avoidance)
NavigationAgent dispose d'un évitement local intégré pour empêcher les agents de se chevaucher. Lorsque l'évitement est activé, l'agent calcule une vitesse sûre qui esquive les autres agents.
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()
Comment ça marche : au lieu d'utiliser la vitesse directement, tu définis nav_agent.velocity sur ta vitesse souhaitée. L'agent calcule ensuite, via le signal velocity_computed, une safe_velocity qui esquive les agents proches. Tu appliques cette vitesse sûre dans le callback.
Navigation dynamique
Tu peux modifier le mesh de navigation à l'exécution pour gérer les portes, les murs destructibles ou un terrain changeant :
# 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
Note sur les performances : générer un mesh de navigation 3D à l'exécution est coûteux. Pour les obstacles dynamiques en 3D, privilégie NavigationObstacle3D plutôt qu'un nouveau baking. En 2D, tu peux basculer NavigationRegion2D.enabled à faible coût.
Motifs courants
IA d'ennemi qui pourchasse le joueur
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()
Trajets de patrouille des PNJ
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()
Clic pour se déplacer (vue de dessus)
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()
Changements de Godot 3 à Godot 4
| Godot 3 | Godot 4 |
|---|---|
Nœud Navigation2D |
Supprimé. Utilise NavigationRegion2D + NavigationAgent2D |
Navigation.get_simple_path() |
NavigationServer2D.map_get_path() |
| Suivi manuel du chemin | NavigationAgent.get_next_path_position() s'en charge |
| Pas d'évitement intégré | NavigationAgent.avoidance_enabled |
| Pas de calques de navigation | 32 calques de navigation par carte |
| Pas d'obstacles | NavigationObstacle2D/3D |