强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Godot 4 GDScript 教程 / 导航与寻路(NavigationServer)

导航与寻路(NavigationServer)

概述

Godot 4 的导航系统基于 NavigationServer 单例,通过导航网格(Navigation Mesh)实现自动寻路。支持 2D/3D 导航、动态障碍物、多层导航和路径平滑。

节点说明
NavigationRegion3D/2D导航区域(包含导航网格)
NavigationAgent3D/2D导航代理(移动寻路)
NavigationObstacle3D/2D动态障碍物
NavigationLink3D/2D导航连接(跳板、电梯)

设置导航区域

extends Node3D

func _ready():
    # 在编辑器中:
    # 1. 添加 NavigationRegion3D 节点
    # 2. 创建 NavigationMesh 资源
    # 3. 设置参数后烘焙
    pass

# 代码中烘焙导航网格
func bake_navigation():
    var region: NavigationRegion3D = $NavigationRegion3D
    # 烘焙需要场景中的 StaticBody3D 有碰撞形状
    region.bake_navigation_mesh()
参数说明默认值
agent_radius代理半径(边缘裁切)0.5
agent_height代理高度(桥检测)1.0
agent_max_slope最大坡度角(度)45
agent_max_climb最大攀爬高度0.25
cell_size体素大小0.3
cell_height体素高度0.2
detail_sampling_distance细节采样距离6.0
detail_sampling_error细节采样误差1.0
filter_baking_aabb烘焙区域全场景

基本寻路

extends CharacterBody3D

@onready var agent: NavigationAgent3D = $NavigationAgent3D
@export var speed: float = 3.0

var target_position: Vector3

func _ready():
    # 设置导航代理参数
    agent.path_desired_distance = 0.5   # 到达路径点的阈值
    agent.target_desired_distance = 1.0 # 到达目标的阈值
    agent.path_max_distance = 50.0      # 最大路径长度
    agent.avoidance_enabled = true      # 启用避障

func set_target(target: Vector3):
    target_position = target
    agent.target_position = target

func _physics_process(delta):
    if agent.is_navigation_finished():
        return

    var next_path_pos = agent.get_next_path_position()
    var direction = (next_path_pos - global_position).normalized()

    velocity = direction * speed

    # 朝向移动方向
    if direction.length() > 0.1:
        look_at(global_position + Vector3(direction.x, 0, direction.z))

    move_and_slide()
func _ready():
    agent.navigation_finished.connect(_on_nav_finished)
    agent.path_changed.connect(_on_path_changed)
    agent.target_reached.connect(_on_target_reached)
    agent.link_reached.connect(_on_link_reached)
    agent.waypoint_reached.connect(_on_waypoint_reached)
    agent.velocity_computed.connect(_on_velocity_computed)

func _on_nav_finished():
    print("到达目标")

func _on_target_reached():
    print("到达目标位置")

func _on_waypoint_reached(details: Dictionary):
    print("到达路径点: ", details)

func _on_velocity_computed(safe_velocity: Vector3):
    # 避障计算后的安全速度
    velocity = safe_velocity
    move_and_slide()

extends Node

func _ready():
    # 获取默认导航地图
    var map = NavigationServer3D.get_maps()[0]

    # 查询路径
    var start = Vector3(0, 0, 0)
    var end = Vector3(10, 0, 10)
    var path = NavigationServer3D.map_get_path(map, start, end, true)
    print("路径点: ", path)

    # 获取最近的导航点
    var closest = NavigationServer3D.map_get_closest_point(map, Vector3(5, 0, 5))
    print("最近导航点: ", closest)

    # 判断点是否在导航网格上
    var is_on_nav = NavigationServer3D.map_get_closest_point(map, global_position)
    var distance = global_position.distance_to(is_on_nav)
    if distance > 1.0:
        print("不在导航网格上")

# 区域查询
func query_region():
    var map = NavigationServer3D.get_maps()[0]
    var regions = NavigationServer3D.map_get_regions(map)
    for region in regions:
        print("区域: ", region)
方法说明
get_maps()获取所有导航地图
map_get_path()查询路径
map_get_closest_point()获取最近导航点
map_get_closest_point_normal()获取最近点法线
map_get_closest_point_owner()获取最近点所属区域
map_get_regions()获取地图中的区域
region_get_path()从区域查询路径
obstacle_set_avoidance_enabled()设置障碍物避障

💡 提示:如果 map_get_path() 返回空数组,说明起始点或目标点不在导航网格上。使用 map_get_closest_point() 将点吸附到最近的导航网格位置。


2D 导航

extends CharacterBody2D

@onready var agent: NavigationAgent2D = $NavigationAgent2D
@export var speed: float = 200.0

func set_target(target: Vector2):
    agent.target_position = target

func _physics_process(delta):
    if agent.is_navigation_finished():
        velocity = Vector2.ZERO
        return

    var next_pos = agent.get_next_path_position()
    var direction = (next_pos - global_position).normalized()

    velocity = direction * speed

    if direction.length() > 0.1:
        rotation = direction.angle()

    move_and_slide()

动态障碍物 Obstacle3D

extends NavigationObstacle3D

func _ready():
    # 设置障碍物参数
    radius = 2.0  # 圆形障碍半径
    # 或使用高度/速度
    height = 3.0
    avoidance_enabled = true

代码创建动态障碍

extends Node3D

func create_obstacle(pos: Vector3, size: float = 1.0):
    var obstacle = NavigationObstacle3D.new()
    obstacle.position = pos
    obstacle.radius = size
    obstacle.avoidance_enabled = true
    add_child(obstacle)

多层导航

不同类型的单位使用不同的导航层:

extends CharacterBody3D

@onready var agent: NavigationAgent3D = $NavigationAgent3D

func _ready():
    # 设置导航层掩码
    # 地面单位使用第 1 层
    agent.navigation_layers = 1  # 二进制:0b0001

    # 飞行单位使用第 2 层
    # agent.navigation_layers = 2  # 二进制:0b0010

    # 水生单位使用第 3 层
    # agent.navigation_layers = 4  # 二进制:0b0100

    # 多层导航区域
    # 在编辑器中为不同 NavigationRegion3D 设置不同的 navigation_layers

AI 巡逻与追击

extends CharacterBody3D

enum State { PATROL, CHASE, SEARCH }
var current_state: State = State.PATROL

@onready var agent: NavigationAgent3D = $NavigationAgent3D
@onready var vision_area: Area3D = $VisionArea

@export var patrol_speed: float = 2.0
@export var chase_speed: float = 4.0
@export var patrol_points: Array[Vector3] = []
@export var detection_range: float = 15.0

var current_patrol_index: int = 0
var player: Node3D = null
var last_known_player_pos: Vector3
var search_timer: float = 0.0

func _ready():
    agent.path_desired_distance = 1.0
    agent.target_desired_distance = 1.5
    agent.avoidance_enabled = true

    vision_area.body_entered.connect(_on_player_entered)
    vision_area.body_exited.connect(_on_player_exited)

    if patrol_points.size() > 0:
        agent.target_position = patrol_points[0]

func _physics_process(delta):
    match current_state:
        State.PATROL:
            patrol(delta)
        State.CHASE:
            chase(delta)
        State.SEARCH:
            search(delta)

func patrol(delta):
    if agent.is_navigation_finished():
        current_patrol_index = (current_patrol_index + 1) % patrol_points.size()
        agent.target_position = patrol_points[current_patrol_index]

    move_toward_target(patrol_speed)
    look_at_target()

func chase(delta):
    if player:
        agent.target_position = player.global_position
        last_known_player_pos = player.global_position

    move_toward_target(chase_speed)
    look_at_target()

    # 检查是否到达最后已知位置
    if not player and agent.is_navigation_finished():
        current_state = State.SEARCH
        search_timer = 5.0

func search(delta):
    search_timer -= delta
    if search_timer <= 0:
        current_state = State.PATROL
        agent.target_position = patrol_points[current_patrol_index]

func move_toward_target(speed: float):
    if agent.is_navigation_finished():
        velocity = Vector3.ZERO
        return

    var next_pos = agent.get_next_path_position()
    var direction = (next_pos - global_position).normalized()
    velocity = direction * speed
    move_and_slide()

func look_at_target():
    if velocity.length() > 0.1:
        var target = global_position + Vector3(velocity.x, 0, velocity.z).normalized()
        look_at(target, Vector3.UP)

func _on_player_entered(body: Node3D):
    if body.is_in_group("player"):
        player = body
        current_state = State.CHASE

func _on_player_exited(body: Node3D):
    if body == player:
        player = null

路径平滑

路径点简化

extends CharacterBody3D

@onready var agent: NavigationAgent3D = $NavigationAgent3D

var smoothed_path: PackedVector3Array = []
var path_index: int = 0

func set_target(target: Vector3):
    agent.target_position = target
    # 获取完整路径
    var raw_path = agent.get_current_navigation_path()
    smoothed_path = smooth_path(raw_path, 0.5)
    path_index = 0

func smooth_path(path: PackedVector3Array, tolerance: float) -> PackedVector3Array:
    if path.size() <= 2:
        return path

    # Ramer-Douglas-Peucker 算法简化路径
    var result = PackedVector3Array()
    result.append(path[0])
    rdp_simplify(path, 0, path.size() - 1, tolerance, result)
    result.append(path[path.size() - 1])
    return result

func rdp_simplify(path: PackedVector3Array, start: int, end: int, tolerance: float, result: PackedVector3Array):
    var max_dist = 0.0
    var max_idx = start

    for i in range(start + 1, end):
        var dist = point_to_line_distance(path[i], path[start], path[end])
        if dist > max_dist:
            max_dist = dist
            max_idx = i

    if max_dist > tolerance:
        rdp_simplify(path, start, max_idx, tolerance, result)
        result.append(path[max_idx])
        rdp_simplify(path, max_idx, end, tolerance, result)

func point_to_line_distance(point: Vector3, line_start: Vector3, line_end: Vector3) -> float:
    var line_vec = line_end - line_start
    var point_vec = point - line_start
    var t = clampf(point_vec.dot(line_vec) / line_vec.length_squared(), 0.0, 1.0)
    var closest = line_start + line_vec * t
    return point.distance_to(closest)

贝塞尔路径平滑

func bezier_smooth(path: PackedVector3Array, segments: int = 5) -> PackedVector3Array:
    var result = PackedVector3Array()
    for i in range(path.size() - 1):
        var p0 = path[maxi(0, i - 1)]
        var p1 = path[i]
        var p2 = path[min(path.size() - 1, i + 1)]
        var p3 = path[min(path.size() - 1, i + 2)]

        for t_step in range(segments + 1):
            var t = float(t_step) / float(segments)
            var point = catmull_rom(p0, p1, p2, p3, t)
            result.append(point)
    return result

func catmull_rom(p0: Vector3, p1: Vector3, p2: Vector3, p3: Vector3, t: float) -> Vector3:
    var t2 = t * t
    var t3 = t2 * t
    return 0.5 * (
        (2 * p1) +
        (-p0 + p2) * t +
        (2 * p0 - 5 * p1 + 4 * p2 - p3) * t2 +
        (-p0 + 3 * p1 - 3 * p2 + p3) * t3
    )

导航性能优化

策略说明
降低 NavigationMesh 细度增大 cell_size
限制同时寻路的代理数分帧计算路径
使用路径缓存相同目标不重复查询
简化导航网格只保留必要区域
禁用远处代理的避障agent.avoidance_enabled = false
使用导航层过滤不同层分开计算
# 分帧寻路:每帧只处理 N 个代理
var agents: Array[NavigationAgent3D] = []
var current_batch: int = 0
const BATCH_SIZE = 5

func _physics_process(delta):
    var start = current_batch
    var end = mini(start + BATCH_SIZE, agents.size())
    for i in range(start, end):
        update_agent(agents[i])
    current_batch = end if end < agents.size() else 0

复杂地图导航策略

多 NavigationRegion 拼接

# 大型地图分块导航
# 每个区域有独立的 NavigationRegion3D
# NavigationServer 自动合并相邻区域

# 导航连接(NavigationLink3D)用于非标准路径
# 例如:跳跃平台、电梯、传送门
extends NavigationLink3D

func _ready():
    # 设置连接起止点
    start_position = Vector3(0, 0, 0)
    end_position = Vector3(0, 3, 5)  # 跳到高处
    # 导航层
    navigation_layers = 1
    # 是否双向
    bidirectional = true
场景说明
跳跃平台从低处跳到高处
电梯垂直移动
传送门瞬间移动
梯子攀爬连接
断桥跨越间隙

游戏开发场景

场景推荐方案
RTS 单位寻路NavigationAgent + 避障
RPG NPC 巡逻巡逻点 + NavigationAgent
塔防怪物路径固定 NavigationRegion
开放世界 AI多层导航 + 路径缓存
2D 地牢探索NavigationRegion2D + 动态烘焙

⚠️ 常见陷阱

  1. 导航网格不覆盖的地方无法寻路,确保目标点在网格上
  2. agent_radius 过大会导致窄通道不可通过
  3. NavigationLink3D 必须在 NavigationRegion3D 的范围内
  4. 动态障碍物不会自动改变导航网格,只是改变避障
  5. map_get_path() 是同步调用,大量调用会卡顿

扩展阅读