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

  1. Tambahkan sebuah node NavigationRegion2D ke scene-mu.
  2. Di Inspector, buat sebuah resource NavigationPolygon baru.
  3. 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 + 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()

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