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
- Dodaj do sceny węzeł NavigationRegion2D.
- W inspektorze utwórz nowy zasób NavigationPolygon.
- 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+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()
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 |