Einführung
Godot 4 hat ein robustes Navigationssystem, das auf NavigationServer2D/3D, NavigationRegion-Nodes und NavigationAgent-Nodes aufbaut. Es ist eine deutliche Verbesserung gegenüber dem System von Godot 3 — flexibler, performanter und viel einfacher für komplexe Szenarien wie dynamische Hindernisse und mehrere Agententypen zu konfigurieren.
Kurzer Überblick: NavigationRegion definiert, wo Agenten gehen können. NavigationAgent übernimmt die Wegfindung für einzelne Entitäten. NavigationServer verwaltet alles im Hintergrund.
Grundkonzepte
- NavigationRegion2D / NavigationRegion3D — Definiert einen begehbaren Bereich mit einem NavigationPolygon (2D) oder NavigationMesh (3D). Du kannst mehrere Regionen haben; der Server führt sie automatisch zusammen.
- NavigationAgent2D / NavigationAgent3D — An einen CharacterBody anhängen, um die Wegfindung zu übernehmen. Berechnet die nächste Pfadposition, übernimmt das Ausweichen und sendet Signale, wenn die Navigation abgeschlossen ist.
- NavigationServer2D / NavigationServer3D — Singleton, das alle Navigationsdaten verwaltet. Du interagierst selten direkt damit, aber es übernimmt Kartenaktualisierungen, Pfadabfragen und Regionsverbindungen.
2D-Navigation einrichten
Schritt 1: NavigationRegion2D hinzufügen
- Füge deiner Szene einen NavigationRegion2D-Node hinzu.
- Erstelle im Inspektor eine neue NavigationPolygon-Ressource.
- Zeichne das begehbare Polygon im 2D-Editor. Das Polygon definiert, wo Agenten gehen können.
TileMap-Integration: Ein NavigationPolygon kann aus TileMap-Daten gebacken werden. Wenn deine Kacheln im TileSet Navigationspolygone definiert haben, füge einfach ein NavigationRegion2D hinzu und backe — es generiert den begehbaren Bereich automatisch aus deinem Kachel-Layout.
Ein NavigationAgent2D hinzufügen
Füge ein NavigationAgent2D als Kind deines CharacterBody2D hinzu. Hier ist ein vollständiges Bewegungsskript:
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()
Wichtig: Der NavigationServer braucht einen Physik-Frame zur Synchronisation. Führe immer await get_tree().physics_frame aus, bevor du die erste Zielposition in _ready() setzt, sonst findet der Agent möglicherweise keinen gültigen Pfad.
Wichtige NavigationAgent2D-Eigenschaften
-
target_position— Wohin der Agent gehen möchte -
path_desired_distance— Wie nah der Agent an jedem Pfadpunkt sein muss, um zum nächsten weiterzugehen -
target_desired_distance— Wie nah der Agent am Ziel sein muss, damit die Navigation als abgeschlossen gilt -
max_speed— Wird für Ausweichberechnungen verwendet (begrenzt deinen Bewegungscode nicht)
3D-Navigation
Das 3D-Navigationssystem funktioniert identisch zum 2D-System, nur mit anderen Node-Typen:
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()
Navigations-Meshes backen
In 3D backst du das Navigations-Mesh normalerweise aus deiner Level-Geometrie, anstatt es von Hand zu zeichnen.
Backen-Konfiguration
Wichtige Eigenschaften in der NavigationMesh-Ressource:
- Agent Radius — Wie weit Agenten von Wänden entfernt bleiben. Größere Werte erzeugen konservativere Pfade.
- Agent Height — Minimale Deckenhöhe für begehbare Bereiche.
- Agent Max Climb — Maximale Stufenhöhe, die Agenten hochgehen können (z. B. Treppen).
- Agent Max Slope — Maximal begehbarer Neigungswinkel in Grad.
# 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!")
Navigations-Ebenen
Mit Navigations-Ebenen kannst du begehbare Bereiche für verschiedene Agententypen trennen. Zum Beispiel können Bodeneinheiten und fliegende Einheiten unterschiedliche Navigations-Meshes haben.
# 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
Eine typische Ebenen-Konfiguration:
- Layer 1 — Bodeneinheiten (Soldaten, Fahrzeuge)
- Layer 2 — Fliegende Einheiten (Drohnen, Vögel)
- Layer 3 — Große Einheiten (nur breite Korridore)
Ausweichverhalten (Avoidance)
NavigationAgent verfügt über eingebautes lokales Ausweichen, um zu verhindern, dass sich Agenten überlappen. Wenn das Ausweichen aktiviert ist, berechnet der Agent eine sichere Geschwindigkeit, die anderen Agenten ausweicht.
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()
Wie es funktioniert: Statt die Geschwindigkeit direkt zu verwenden, setzt du nav_agent.velocity auf deine gewünschte Geschwindigkeit. Der Agent berechnet dann über das Signal velocity_computed eine safe_velocity, die nahen Agenten ausweicht. Diese sichere Geschwindigkeit wendest du im Callback an.
Dynamische Navigation
Du kannst das Navigations-Mesh zur Laufzeit ändern, um Türen, zerstörbare Wände oder sich änderndes Terrain zu handhaben:
# 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
Performance-Hinweis: Das Backen eines 3D-Navigations-Meshes zur Laufzeit ist aufwendig. Für dynamische Hindernisse in 3D solltest du NavigationObstacle3D statt eines erneuten Backens bevorzugen. In 2D kannst du NavigationRegion2D.enabled kostengünstig umschalten.
Häufige Muster
Gegner-KI, die den Spieler verfolgt
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-Patrouillenrouten
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()
Klick-zum-Bewegen (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()
Änderungen von Godot 3 zu Godot 4
| Godot 3 | Godot 4 |
|---|---|
Navigation2D-Node |
Entfernt. Verwende NavigationRegion2D + NavigationAgent2D |
Navigation.get_simple_path() |
NavigationServer2D.map_get_path() |
| Manuelles Pfadverfolgen | NavigationAgent.get_next_path_position() übernimmt das |
| Kein eingebautes Ausweichen | NavigationAgent.avoidance_enabled |
| Keine Navigations-Ebenen | 32 Navigations-Ebenen pro Karte |
| Keine Hindernisse | NavigationObstacle2D/3D |