3D 导航概述

Godot 提供了多种对象、类和服务器,可帮助 2D 和 3D 游戏实现基于栅格(Grid)或网格(Mesh)的导航和寻路。下文将对 Godot 中与 3D 场景导航相关的对象及其主要用途进行概述。

Godot 为 3D 导航提供了如下对象和类:

  • Astar3D

    Astar3D 对象能够在由具有权重的构成的图中查找最短路径。

    AStar3D 类最适合的是基于单元格的 3D 游戏,角色不需要到达区域中的任意位置,只需要到达预先指定的一些独立位置。

  • NavigationServer3D

    NavigationServer3D 提供了强大的服务器 API,能够在区域中查找两个位置之间的最短路径,区域使用导航网格定义。

    NavigationServer 最适合的是要求角色能够到达区域中任意位置的 3D 实时游戏,区域由导航网格定义。基于网格的导航能够轻松扩展到大型游戏世界,因为大型区域通常能够使用单一多边形定义,如果换成栅格则会需要定义许许多多的单元格。

    NavigationServer 中存放了不同的导航地图,每一张地图都由若干区块组成,区块中存放的是导航网格数据。在地图上放置代理就能够进行避障计算。与服务器通信时,使用 RID 来引用内部的地图、区块和代理。

    NavigationServer 中可用的 RID 类型如下。
    • 导航地图 RID

      引用指定的导航地图,地图中存放的是区块和代理。地图会尝试将区块中的导航网格根据距离进行合并。每一个物理帧,地图都会同步区块和代理。

    • 导航区块 RID

      引用指定的导航区块,区块中存放的是导航网格数据。使用导航层位掩码可以对区块进行启用/禁用,限制其使用。

    • 导航链接 RID

      引用指定的导航链接,能够将两个导航网格上的位置进行连接,无视距离。

    • 导航代理 RID

      引用指定的避障代理,避障时使用的是半径值。

    • 导航障碍物 RID

      引用指定的避障障碍物,会对代理的避障速度产生影响和约束。

下列场景树节点可以辅助对 NavigationServer3D API 的使用。

  • NavigationRegion3D 节点

    存放 Navigation Mesh 资源的节点,该资源定义的是 NavigationServer3D 中的导航网格。

    • 区块可以启用/禁用。

    • 通过 navigation_layers 掩码,可以对其在寻路中的使用做进一步的限制。

    • NavigationServer3D 会根据距离将不同的导航网格合并成一个导航网格。

  • NavigationLink3D 节点

    将两个导航网格上的位置进行连接的节点,无视距离,可用于寻路。

    • 链接可以启用/禁用。

    • 链接可以设为单向或双向。

    • 通过 navigation_layers 掩码,可以对其在寻路中的使用做进一步的限制。

    链接会告诉寻路存在这样的连接、相关的消耗如何。实际的代理处理以及移动需要在自定义脚本中实现。

  • NavigationAgent3D 节点

    方便调用寻路和避障所需的常规 NavigationServer3D API 的辅助节点。该节点的父节点应该继承自 Node3D。

  • NavigationObstacle3D 节点

    可用于影响和约束启用躲避的代理的躲避速度的节点。此节点不影响代理的寻路。你需要为此更改导航网格。

3D 导航网格由以下资源定义:

  • NavigationMesh 资源

    存放 3D 导航网格数据的资源,提供了 3D 几何体的烘焙选项,既能够在编辑器中定义导航区域,也能够在运行时定义。

    • NavigationRegion3D 节点使用该资源定义其导航区域。

    • NavigationServer3D 使用该资源更新各个区块的导航网格。

    • GridMap 编辑器会在栅格单元格中存在对导航网格的定义时使用该资源。

参见

可以使用 3D 导航演示项目了解 3D 导航如何运作。

3D 场景的设置

下列步骤演示的是最小可行的 3D 导航的基础设置,使用 NavigationServer3D 和 NavigationAgent3D 进行路径移动。

  1. 在场景中添加一个 NavigationRegion3D 节点。

  2. 单击该区块节点,向该节点添加一个新的 NavigationMesh 资源。

    ../../_images/nav_3d_min_setup_step1.png
  3. 将一个新的 MeshInstance3D 节点添加为该区块节点的子节点。

  4. 选中该 MeshInstance3D 节点,添加一个新的 PlaneMesh 并将其 XY 大小设为 10。

  5. 再次选中该区块节点,点击顶栏中的“烘焙导航网格”按钮。

    ../../_images/nav_3d_min_setup_step2.png
  6. 现在就会显示出透明的导航网格,悬浮在 PlaneMesh 上方。

    ../../_images/nav_3d_min_setup_step3.png
  7. 在场景中添加一个 CharacterBody3D 节点,设置基础的碰撞形状,添加一些网格方便观察。

  8. 在该角色节点下添加一个 NavigationAgent3D 节点。

    ../../_images/nav_3d_min_setup_step4.webp
  9. 为 CharacterBody3D 节点添加一个脚本,内容如下。场景完全加载后,我们确保设置移动目标,NavigationServer 有时间进行同步。另外,添加一个 Camera3D、一些灯光以及环境,这样才能够看到东西。

extends CharacterBody3D

var movement_speed: float = 2.0
var movement_target_position: Vector3 = Vector3(-3.0,0.0,2.0)

@onready var navigation_agent: NavigationAgent3D = $NavigationAgent3D

func _ready():
    # These values need to be adjusted for the actor's speed
    # and the navigation layout.
    navigation_agent.path_desired_distance = 0.5
    navigation_agent.target_desired_distance = 0.5

    # Make sure to not await during _ready.
    actor_setup.call_deferred()

func actor_setup():
    # Wait for the first physics frame so the NavigationServer can sync.
    await get_tree().physics_frame

    # Now that the navigation map is no longer empty, set the movement target.
    set_movement_target(movement_target_position)

func set_movement_target(movement_target: Vector3):
    navigation_agent.set_target_position(movement_target)

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

    var current_agent_position: Vector3 = global_position
    var next_path_position: Vector3 = navigation_agent.get_next_path_position()

    velocity = current_agent_position.direction_to(next_path_position) * movement_speed
    move_and_slide()

备注

第一帧的时候,NavigationServer 上的地图还没有同步区块数据,请求路径时都会返回空。在脚本中等待一帧就可以让 NavigationServer 进行同步。