Pengantar
Godot 4 memiliki sistem navigasi yang tangguh yang dibangun di atas NavigationServer2D/3D, node NavigationRegion, dan node NavigationAgent. Ini adalah peningkatan signifikan dibanding sistem Godot 3 — lebih fleksibel, lebih berkinerja, dan jauh lebih mudah dikonfigurasi untuk skenario kompleks seperti rintangan dinamis dan beberapa jenis agen.
Gambaran singkat: NavigationRegion mendefinisikan tempat agen dapat berjalan. NavigationAgent menangani pathfinding untuk masing-masing entitas. NavigationServer mengelola semuanya di balik layar.
Konsep Inti
- NavigationRegion2D / NavigationRegion3D — Mendefinisikan area yang dapat dilalui menggunakan NavigationPolygon (2D) atau NavigationMesh (3D). Kamu dapat memiliki beberapa region; server menggabungkannya secara otomatis.
- NavigationAgent2D / NavigationAgent3D — Pasang ke sebuah CharacterBody untuk menangani pathfinding. Menghitung posisi path berikutnya, menangani avoidance, dan memancarkan signal saat navigasi selesai.
- NavigationServer2D / NavigationServer3D — Singleton yang mengelola semua data navigasi. Kamu jarang berinteraksi langsung dengannya, tetapi ia menangani pembaruan map, kueri path, dan koneksi region.
Menyiapkan Navigasi 2D
Langkah 1: Tambahkan NavigationRegion2D
- Tambahkan sebuah node NavigationRegion2D ke scene-mu.
- Di Inspector, buat sebuah resource NavigationPolygon baru.
- Gambar poligon yang dapat dilalui di editor 2D. Poligon tersebut mendefinisikan tempat agen dapat berjalan.
Integrasi TileMap: NavigationPolygon dapat di-bake dari data TileMap. Jika tile-mu memiliki poligon navigasi yang didefinisikan di TileSet, cukup tambahkan sebuah NavigationRegion2D dan bake — ia akan secara otomatis menghasilkan area yang dapat dilalui dari layout tile-mu.
Menambahkan NavigationAgent2D
Tambahkan sebuah NavigationAgent2D sebagai child dari CharacterBody2D-mu. Berikut skrip gerakan lengkapnya:
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()
Penting: NavigationServer memerlukan satu physics frame untuk sinkronisasi. Selalu jalankan await get_tree().physics_frame sebelum menetapkan posisi target pertama di _ready(), jika tidak agen mungkin tidak menemukan path yang valid.
Properti NavigationAgent2D Utama
-
target_position— Ke mana agen ingin pergi -
path_desired_distance— Seberapa dekat agen harus dengan setiap titik path untuk maju ke titik berikutnya -
target_desired_distance— Seberapa dekat agen harus dengan target agar navigasi dianggap selesai -
max_speed— Digunakan untuk perhitungan avoidance (tidak membatasi kode gerakanmu)
Navigasi 3D
Sistem navigasi 3D bekerja sama persis dengan 2D, hanya dengan jenis node yang berbeda:
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()
Mem-bake Navigation Mesh
Untuk 3D, kamu biasanya mem-bake navigation mesh dari geometri level-mu alih-alih menggambarnya secara manual.
Konfigurasi Baking
Properti utama dalam resource NavigationMesh:
- Agent Radius — Seberapa jauh agen menjaga jarak dari dinding. Nilai yang lebih besar menghasilkan path yang lebih konservatif.
- Agent Height — Tinggi langit-langit minimum untuk area yang dapat dilalui.
- Agent Max Climb — Tinggi langkah maksimum yang dapat dinaiki agen (mis. tangga).
- Agent Max Slope — Sudut kemiringan maksimum yang dapat dilalui dalam derajat.
# 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!")
Navigation Layer
Navigation layer memungkinkan kamu memisahkan area yang dapat dilalui untuk jenis agen yang berbeda. Misalnya, unit darat dan unit terbang dapat memiliki navigation mesh yang berbeda.
# 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
Konfigurasi layer yang umum:
- Layer 1 — Unit darat (tentara, kendaraan)
- Layer 2 — Unit terbang (drone, burung)
- Layer 3 — Unit besar (hanya koridor lebar)
Avoidance (Penghindaran)
NavigationAgent memiliki local avoidance bawaan untuk mencegah agen tumpang tindih. Saat avoidance diaktifkan, agen menghitung kecepatan yang aman yang mengarahkan menjauh dari agen lain.
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()
Cara kerjanya: Alih-alih menggunakan velocity secara langsung, kamu menetapkan nav_agent.velocity ke kecepatan yang kamu inginkan. Agen kemudian menghitung sebuah safe_velocity melalui signal velocity_computed yang menghindari agen-agen di dekatnya. Kamu menerapkan kecepatan aman ini di dalam callback.
Navigasi Dinamis
Kamu dapat memodifikasi navigation mesh pada saat runtime untuk menangani pintu, dinding yang dapat dihancurkan, atau terrain yang berubah:
# 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
Catatan performa: Mem-bake navigation mesh 3D pada saat runtime mahal secara komputasi. Untuk rintangan dinamis di 3D, lebih baik gunakan NavigationObstacle3D alih-alih mem-bake ulang. Untuk 2D, kamu dapat mengalihkan NavigationRegion2D.enabled dengan murah.
Pola Umum
AI Musuh yang Mengejar Pemain
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()
Rute Patroli 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()
Klik-untuk-Bergerak (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()
Perubahan dari Godot 3 ke Godot 4
| Godot 3 | Godot 4 |
|---|---|
Node Navigation2D |
Dihapus. Gunakan NavigationRegion2D + NavigationAgent2D |
Navigation.get_simple_path() |
NavigationServer2D.map_get_path() |
| Mengikuti path secara manual | NavigationAgent.get_next_path_position() menanganinya |
| Tidak ada avoidance bawaan | NavigationAgent.avoidance_enabled |
| Tidak ada navigation layer | 32 navigation layer per map |
| Tidak ada rintangan | NavigationObstacle2D/3D |