Giới thiệu
Godot 4 có một hệ thống điều hướng mạnh mẽ được xây dựng dựa trên NavigationServer2D/3D, các node NavigationRegion và các node NavigationAgent. Đây là một bước tiến đáng kể so với hệ thống của Godot 3 — linh hoạt hơn, hiệu năng cao hơn và dễ cấu hình hơn nhiều cho các kịch bản phức tạp như vật cản động và nhiều loại agent khác nhau.
Tổng quan nhanh: NavigationRegion xác định nơi agent có thể di chuyển. NavigationAgent xử lý việc tìm đường cho từng thực thể riêng lẻ. NavigationServer quản lý mọi thứ ở phía hậu trường.
Khái niệm cốt lõi
- NavigationRegion2D / NavigationRegion3D — Xác định một vùng có thể di chuyển bằng NavigationPolygon (2D) hoặc NavigationMesh (3D). Bạn có thể có nhiều vùng; server sẽ tự động hợp nhất chúng.
- NavigationAgent2D / NavigationAgent3D — Gắn vào một CharacterBody để xử lý việc tìm đường. Tính toán vị trí tiếp theo trên đường đi, xử lý việc tránh va chạm và phát ra tín hiệu khi hoàn tất điều hướng.
- NavigationServer2D / NavigationServer3D — Singleton quản lý tất cả dữ liệu điều hướng. Bạn hiếm khi tương tác trực tiếp với nó, nhưng nó xử lý việc cập nhật bản đồ, các truy vấn đường đi và kết nối giữa các vùng.
Thiết lập điều hướng 2D
Bước 1: Thêm NavigationRegion2D
- Thêm một node NavigationRegion2D vào scene của bạn.
- Trong Inspector, tạo một tài nguyên NavigationPolygon mới.
- Vẽ polygon có thể di chuyển trong trình chỉnh sửa 2D. Polygon này xác định nơi agent có thể di chuyển.
Tích hợp với TileMap: NavigationPolygon có thể được bake từ dữ liệu TileMap. Nếu các tile của bạn có polygon điều hướng được định nghĩa trong TileSet, chỉ cần thêm một NavigationRegion2D và bake — nó sẽ tự động tạo ra vùng có thể di chuyển từ bố cục tile của bạn.
Thêm một NavigationAgent2D
Thêm một NavigationAgent2D làm con của CharacterBody2D. Dưới đây là một script di chuyển hoàn chỉnh:
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()
Quan trọng: NavigationServer cần một khung hình vật lý để đồng bộ hóa. Luôn thực hiện await get_tree().physics_frame trước khi đặt vị trí đích đầu tiên trong _ready(), nếu không agent có thể không tìm được đường đi hợp lệ.
Các thuộc tính quan trọng của NavigationAgent2D
-
target_position— Nơi agent muốn đến -
path_desired_distance— Agent cần ở gần mỗi điểm trên đường đi bao nhiêu để tiến tới điểm tiếp theo -
target_desired_distance— Agent cần ở gần đích bao nhiêu để coi như đã hoàn tất điều hướng -
max_speed— Dùng cho các tính toán tránh va chạm (không giới hạn code di chuyển của bạn)
Điều hướng 3D
Hệ thống điều hướng 3D hoạt động giống hệt như 2D, chỉ khác ở loại node:
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()
Bake navigation mesh
Với 3D, bạn thường bake navigation mesh từ hình học của màn chơi thay vì vẽ nó bằng tay.
Cấu hình bake
Các thuộc tính quan trọng trong tài nguyên NavigationMesh:
- Agent Radius — Agent giữ khoảng cách bao xa so với tường. Giá trị lớn hơn tạo ra các đường đi thận trọng hơn.
- Agent Height — Chiều cao trần tối thiểu cho các vùng có thể di chuyển.
- Agent Max Climb — Độ cao bậc thang tối đa mà agent có thể bước lên (ví dụ: cầu thang).
- Agent Max Slope — Góc dốc tối đa có thể di chuyển được, tính bằng độ.
# 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!")
Lớp điều hướng
Lớp điều hướng cho phép bạn tách biệt các vùng di chuyển cho những loại agent khác nhau. Ví dụ, đơn vị mặt đất và đơn vị bay có thể có các navigation mesh khác nhau.
# 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
Một thiết lập lớp điển hình:
- Lớp 1 — Đơn vị mặt đất (lính, xe cộ)
- Lớp 2 — Đơn vị bay (drone, chim)
- Lớp 3 — Đơn vị lớn (chỉ đi được ở các hành lang rộng)
Tránh va chạm (Avoidance)
NavigationAgent có cơ chế tránh va chạm cục bộ tích hợp sẵn để ngăn các agent chồng lên nhau. Khi bật tính năng tránh va chạm, agent sẽ tính toán một vận tốc an toàn để điều hướng tránh xa các agent khác.
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()
Cách hoạt động: Thay vì dùng vận tốc trực tiếp, bạn đặt nav_agent.velocity thành vận tốc mong muốn. Agent sau đó tính toán một safe_velocity thông qua tín hiệu velocity_computed để tránh các agent lân cận. Bạn áp dụng vận tốc an toàn này trong callback.
Điều hướng động
Bạn có thể chỉnh sửa navigation mesh trong lúc chạy để xử lý cửa, tường có thể phá hủy hoặc địa hình thay đổi:
# 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
Lưu ý về hiệu năng: Bake navigation mesh 3D trong lúc chạy rất tốn kém. Với vật cản động trong 3D, hãy ưu tiên dùng NavigationObstacle3D thay vì bake lại. Với 2D, bạn có thể bật/tắt NavigationRegion2D.enabled với chi phí thấp.
Các mẫu thường gặp
AI kẻ địch đuổi theo người chơi
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()
Lộ trình tuần tra của 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()
Click-để-di-chuyển (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()
Những thay đổi từ Godot 3 sang Godot 4
| Godot 3 | Godot 4 |
|---|---|
Navigation2D node |
Đã bị loại bỏ. Dùng NavigationRegion2D + NavigationAgent2D |
Navigation.get_simple_path() |
NavigationServer2D.map_get_path() |
| Tự viết logic đi theo đường | NavigationAgent.get_next_path_position() xử lý việc đó |
| Không có tránh va chạm tích hợp | NavigationAgent.avoidance_enabled |
| Không có lớp điều hướng | 32 lớp điều hướng cho mỗi bản đồ |
| Không có vật cản | NavigationObstacle2D/3D |