Wprowadzenie

Godot 4 ma solidny system nawigacji zbudowany na NavigationServer2D/3D, węzłach NavigationRegion i węzłach NavigationAgent. To znaczące ulepszenie względem systemu z Godot 3 — bardziej elastyczne, wydajniejsze i znacznie łatwiejsze do skonfigurowania w złożonych scenariuszach, takich jak dynamiczne przeszkody i wiele typów agentów.

Szybki przegląd: NavigationRegion definiuje, gdzie agenci mogą chodzić. NavigationAgent obsługuje wyszukiwanie ścieżek dla pojedynczych bytów. NavigationServer zarządza wszystkim w tle.

Podstawowe pojęcia

  • NavigationRegion2D / NavigationRegion3D — Definiuje obszar do chodzenia przy użyciu NavigationPolygon (2D) lub NavigationMesh (3D). Możesz mieć wiele regionów; serwer łączy je automatycznie.
  • NavigationAgent2D / NavigationAgent3D — Dołączany do CharacterBody w celu obsługi wyszukiwania ścieżek. Oblicza następną pozycję ścieżki, obsługuje unikanie kolizji i emituje sygnały po zakończeniu nawigacji.
  • NavigationServer2D / NavigationServer3D — Singleton, który zarządza wszystkimi danymi nawigacji. Rzadko wchodzisz z nim w bezpośrednią interakcję, ale obsługuje aktualizacje mapy, zapytania o ścieżki i połączenia regionów.

Konfiguracja nawigacji 2D

Krok 1: Dodaj NavigationRegion2D

  1. Dodaj do sceny węzeł NavigationRegion2D.
  2. W inspektorze utwórz nowy zasób NavigationPolygon.
  3. Narysuj wielokąt do chodzenia w edytorze 2D. Wielokąt definiuje, gdzie agenci mogą chodzić.

Integracja z TileMap: NavigationPolygon można wypalić z danych TileMap. Jeśli Twoje kafelki mają zdefiniowane wielokąty nawigacji w TileSet, wystarczy dodać NavigationRegion2D i wypalić — obszar do chodzenia zostanie automatycznie wygenerowany z układu kafelków.

Dodawanie NavigationAgent2D

Dodaj NavigationAgent2D jako element potomny swojego CharacterBody2D. Oto kompletny skrypt ruchu:

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()

Ważne: NavigationServer potrzebuje jednej klatki fizyki do synchronizacji. Zawsze wykonuj await get_tree().physics_frame przed ustawieniem pierwszej pozycji celu w _ready(), w przeciwnym razie agent może nie znaleźć prawidłowej ścieżki.

Kluczowe właściwości NavigationAgent2D

  • target_position — Dokąd agent chce dotrzeć
  • path_desired_distance — Jak blisko każdego punktu ścieżki musi być agent, aby przejść do następnego
  • target_desired_distance — Jak blisko celu musi być agent, aby nawigacja została uznana za zakończoną
  • max_speed — Używana do obliczeń unikania kolizji (nie ogranicza kodu ruchu)

Nawigacja 3D

System nawigacji 3D działa identycznie jak 2D, tylko z innymi typami węzłów:

  • 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()

Wypalanie siatek nawigacji

W 3D zazwyczaj wypalasz siatkę nawigacji z geometrii poziomu, zamiast rysować ją ręcznie.

Konfiguracja wypalania

Kluczowe właściwości w zasobie NavigationMesh:

  • Agent Radius — Jak daleko agenci pozostają od ścian. Większe wartości tworzą bardziej zachowawcze ścieżki.
  • Agent Height — Minimalna wysokość sufitu dla obszarów do chodzenia.
  • Agent Max Climb — Maksymalna wysokość stopnia, na który agenci mogą wejść (np. schody).
  • Agent Max Slope — Maksymalny kąt nachylenia do chodzenia w stopniach.
# 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!")

Warstwy nawigacji

Warstwy nawigacji pozwalają rozdzielić obszary do chodzenia dla różnych typów agentów. Na przykład jednostki naziemne i latające mogą mieć różne siatki nawigacji.

# 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

Typowa konfiguracja warstw:

  • Layer 1 — Jednostki naziemne (żołnierze, pojazdy)
  • Layer 2 — Jednostki latające (drony, ptaki)
  • Layer 3 — Duże jednostki (tylko szerokie korytarze)

Unikanie kolizji (Avoidance)

NavigationAgent ma wbudowane lokalne unikanie kolizji, które zapobiega nakładaniu się agentów. Gdy unikanie jest włączone, agent oblicza bezpieczną prędkość, która odsuwa go od innych agentów.

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()

Jak to działa: Zamiast używać prędkości bezpośrednio, ustawiasz nav_agent.velocity na żądaną prędkość. Agent następnie oblicza safe_velocity za pomocą sygnału velocity_computed, która omija pobliskich agentów. Tę bezpieczną prędkość stosujesz w wywołaniu zwrotnym.

Nawigacja dynamiczna

Możesz modyfikować siatkę nawigacji w czasie działania, aby obsłużyć drzwi, niszczalne ściany lub zmieniający się teren:

# 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

Uwaga o wydajności: Wypalanie siatki nawigacji 3D w czasie działania jest kosztowne. Dla dynamicznych przeszkód w 3D preferuj użycie NavigationObstacle3D zamiast ponownego wypalania. W 2D możesz tanio przełączać NavigationRegion2D.enabled.

Częste wzorce

AI wroga podążająca za graczem

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()

Trasy patrolowe 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()

Ruch po kliknięciu (widok z góry)

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()

Zmiany z Godot 3 na Godot 4

Godot 3 Godot 4
Węzeł Navigation2D Usunięty. Użyj NavigationRegion2D + NavigationAgent2D
Navigation.get_simple_path() NavigationServer2D.map_get_path()
Ręczne podążanie po ścieżce NavigationAgent.get_next_path_position() to obsługuje
Brak wbudowanego unikania kolizji NavigationAgent.avoidance_enabled
Brak warstw nawigacji 32 warstwy nawigacji na mapę
Brak przeszkód NavigationObstacle2D/3D