บทนำ

Godot 4 มีระบบนำทางที่แข็งแกร่งซึ่งสร้างขึ้นบน NavigationServer2D/3D, โหนด NavigationRegion และโหนด NavigationAgent ซึ่งเป็นการพัฒนาที่สำคัญเหนือระบบของ Godot 3 — ยืดหยุ่นกว่า มีประสิทธิภาพมากกว่า และตั้งค่าได้ง่ายกว่ามากสำหรับสถานการณ์ที่ซับซ้อน เช่น สิ่งกีดขวางแบบไดนามิกและเอเจนต์หลายประเภท

ภาพรวมโดยย่อ: NavigationRegion กำหนดตำแหน่งที่เอเจนต์สามารถเดินได้ NavigationAgent จัดการการค้นหาเส้นทางสำหรับแต่ละเอนทิตี NavigationServer จัดการทุกอย่างเบื้องหลัง

แนวคิดหลัก

  • NavigationRegion2D / NavigationRegion3D — กำหนดพื้นที่ที่เดินได้โดยใช้ NavigationPolygon (2D) หรือ NavigationMesh (3D) คุณสามารถมีได้หลายรีเจียน เซิร์ฟเวอร์จะรวมเข้าด้วยกันโดยอัตโนมัติ
  • NavigationAgent2D / NavigationAgent3D — แนบเข้ากับ CharacterBody เพื่อจัดการการค้นหาเส้นทาง คำนวณตำแหน่งเส้นทางถัดไป จัดการการหลบหลีก และส่งสัญญาณเมื่อการนำทางเสร็จสิ้น
  • NavigationServer2D / NavigationServer3D — ซิงเกิลตันที่จัดการข้อมูลการนำทางทั้งหมด คุณแทบไม่ต้องโต้ตอบกับมันโดยตรง แต่มันจัดการการอัปเดตแมป การสอบถามเส้นทาง และการเชื่อมต่อรีเจียน

การตั้งค่าการนำทาง 2D

ขั้นตอนที่ 1: เพิ่ม NavigationRegion2D

  1. เพิ่มโหนด NavigationRegion2D ลงในซีนของคุณ
  2. ในอินสเปกเตอร์ สร้างรีซอร์ส NavigationPolygon ใหม่
  3. วาดโพลีกอนที่เดินได้ในเอดิเตอร์ 2D โพลีกอนกำหนดตำแหน่งที่เอเจนต์ สามารถ เดินได้

การผสานรวมกับ TileMap: NavigationPolygon สามารถเบคจากข้อมูล TileMap ได้ หากไทล์ของคุณมีโพลีกอนการนำทางที่กำหนดไว้ใน TileSet เพียงเพิ่ม NavigationRegion2D แล้วเบค — มันจะสร้างพื้นที่ที่เดินได้จากเลย์เอาต์ไทล์ของคุณโดยอัตโนมัติ

การเพิ่ม NavigationAgent2D

เพิ่ม NavigationAgent2D เป็นลูกของ CharacterBody2D ของคุณ นี่คือสคริปต์การเคลื่อนที่ฉบับสมบูรณ์:

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

สำคัญ: NavigationServer ต้องการหนึ่งเฟรมฟิสิกส์เพื่อซิงโครไนซ์ ควร await get_tree().physics_frame เสมอก่อนตั้งค่าตำแหน่งเป้าหมายแรกใน _ready() มิฉะนั้นเอเจนต์อาจไม่พบเส้นทางที่ถูกต้อง

คุณสมบัติหลักของ NavigationAgent2D

  • target_position — ตำแหน่งที่เอเจนต์ต้องการไป
  • path_desired_distance — เอเจนต์ต้องอยู่ใกล้แต่ละจุดเส้นทางแค่ไหนเพื่อเลื่อนไปยังจุดถัดไป
  • target_desired_distance — เอเจนต์ต้องอยู่ใกล้เป้าหมายแค่ไหนจึงจะถือว่าการนำทางเสร็จสิ้น
  • max_speed — ใช้สำหรับการคำนวณการหลบหลีก (ไม่จำกัดโค้ดการเคลื่อนที่ของคุณ)

การนำทาง 3D

ระบบนำทาง 3D ทำงานเหมือนกับ 2D ทุกประการ เพียงแค่ใช้ประเภทโหนดที่ต่างกัน:

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

การเบค Navigation Mesh

สำหรับ 3D โดยทั่วไปคุณจะเบค navigation mesh จากเรขาคณิตของด่านแทนที่จะวาดด้วยมือ

การตั้งค่าการเบค

คุณสมบัติหลักในรีซอร์ส NavigationMesh:

  • Agent Radius — เอเจนต์อยู่ห่างจากกำแพงแค่ไหน ค่ายิ่งมากยิ่งสร้างเส้นทางที่ระมัดระวังมากขึ้น
  • Agent Height — ความสูงเพดานขั้นต่ำสำหรับพื้นที่ที่เดินได้
  • Agent Max Climb — ความสูงขั้นบันไดสูงสุดที่เอเจนต์เดินขึ้นได้ (เช่น บันได)
  • Agent Max Slope — มุมความชันสูงสุดที่เดินได้ในหน่วยองศา
# 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 mesh ที่แตกต่างกัน

# 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

การตั้งค่าเลเยอร์ทั่วไป:

  • Layer 1 — หน่วยภาคพื้นดิน (ทหาร ยานพาหนะ)
  • Layer 2 — หน่วยบิน (โดรน นก)
  • Layer 3 — หน่วยขนาดใหญ่ (เฉพาะทางเดินกว้าง)

การหลบหลีก (Avoidance)

NavigationAgent มีการหลบหลีกในระดับท้องถิ่นในตัวเพื่อป้องกันไม่ให้เอเจนต์ซ้อนทับกัน เมื่อเปิดใช้การหลบหลีก เอเจนต์จะคำนวณความเร็วที่ปลอดภัยซึ่งหลบหนีจากเอเจนต์อื่น

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

วิธีการทำงาน: แทนที่จะใช้ความเร็วโดยตรง คุณตั้งค่า nav_agent.velocity เป็นความเร็วที่ต้องการ จากนั้นเอเจนต์จะคำนวณ safe_velocity ผ่านสัญญาณ velocity_computed ที่หลบหลีกเอเจนต์ที่อยู่ใกล้เคียง คุณนำความเร็วที่ปลอดภัยนี้ไปใช้ในคอลแบ็ก

การนำทางแบบไดนามิก

คุณสามารถแก้ไข navigation mesh ขณะรันไทม์เพื่อจัดการกับประตู กำแพงที่ทำลายได้ หรือภูมิประเทศที่เปลี่ยนแปลง:

# 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

หมายเหตุด้านประสิทธิภาพ: การเบค navigation mesh 3D ขณะรันไทม์มีค่าใช้จ่ายสูง สำหรับสิ่งกีดขวางแบบไดนามิกใน 3D ควรใช้ NavigationObstacle3D แทนการเบคใหม่ สำหรับ 2D คุณสามารถสลับ NavigationRegion2D.enabled ได้อย่างประหยัด

รูปแบบที่พบบ่อย

AI ศัตรูที่ติดตามผู้เล่น

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

เส้นทางลาดตระเวนของ 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()

คลิกเพื่อเคลื่อนที่ (มุมมองบนลงล่าง)

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

การเปลี่ยนแปลงจาก Godot 3 เป็น Godot 4

Godot 3 Godot 4
Navigation2D node ถูกลบออก ใช้ NavigationRegion2D + NavigationAgent2D
Navigation.get_simple_path() NavigationServer2D.map_get_path()
การติดตามเส้นทางด้วยตนเอง NavigationAgent.get_next_path_position() จัดการให้
ไม่มีการหลบหลีกในตัว NavigationAgent.avoidance_enabled
ไม่มีเลเยอร์การนำทาง 32 เลเยอร์การนำทางต่อแมป
ไม่มีสิ่งกีดขวาง NavigationObstacle2D/3D