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

  1. Füge deiner Szene einen NavigationRegion2D-Node hinzu.
  2. Erstelle im Inspektor eine neue NavigationPolygon-Ressource.
  3. 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 + 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()

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