简介

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 需要一个物理帧来完成同步。在 _ready() 中设置第一个目标位置之前,务必先执行 await get_tree().physics_frame,否则智能体可能找不到有效路径。

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

烘焙导航网格

对于 3D,你通常会从关卡几何体烘焙导航网格,而不是手动绘制。

烘焙配置

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!")

导航图层

导航图层让你能够为不同的智能体类型分离可行走区域。例如,地面单位和飞行单位可以拥有不同的导航网格。

# 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 设置为你期望的速度。随后智能体通过 velocity_computed 信号计算出一个能避开附近智能体的 safe_velocity。你在回调中应用这个安全速度。

动态导航

你可以在运行时修改导航网格,以应对门、可破坏的墙壁或变化的地形:

# 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

性能提示: 在运行时烘焙 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 节点 已移除。改用 NavigationRegion2D + NavigationAgent2D
Navigation.get_simple_path() NavigationServer2D.map_get_path()
手动路径跟随 NavigationAgent.get_next_path_position() 处理
无内置避让 NavigationAgent.avoidance_enabled
无导航图层 每张地图 32 个导航图层
无障碍物 NavigationObstacle2D/3D