Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

2D 导航概述

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

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

  • Astar2D

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

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

  • AstarGrid2D

    AstarGrid2D 是 AStar2D 的变体,专门用于部分 2D 网格。

    能够使用 AstarGrid2D 的地方 AstarGrid2D 用起来更简单,因为不需要手动创建点,也不需要手动进行连接。

  • NavigationServer2D

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

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

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

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

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

    • 导航区块 RID

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

    • 导航链接 RID

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

    • 导航代理 RID

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

    • 导航障碍物 RID

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

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

  • NavigationRegion2D 节点

    存放 NavigationPolygon 资源的节点,该资源定义的是 NavigationServer2D 中的导航网格。

    • 区块可以启用/禁用。

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

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

  • NavigationLink2D 节点

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

    • 链接可以启用/禁用。

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

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

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

  • NavigationAgent2D 节点

    可选的辅助节点,用于为继承自 Node2D 的父节点提供寻路和避障所需的常规 NavigationServer2D API 调用。请将这个节点放在继承自 Node2D 的父节点下。

  • NavigationObstacle2D 节点

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

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

  • NavigationPolygon 资源

    存放 2D 导航网格数据的资源,提供了多边形绘制工具,既能够在编辑器中定义导航区域,也能够在运行时定义。

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

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

    • TileSet 编辑器会定义图块的导航区域时在内部创建并使用该资源。

参见

可以使用 2D 导航演示项目使用 AStarGrid2D 进行基于栅格的导航 演示项目了解 2D 导航如何运作。

2D 场景的设置

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

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

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

    ../../_images/nav_2d_min_setup_step1.png
  3. 使用 NavigationPolygon 绘制工具定义可移动导航区域。然后点击工具栏上的 烘焙 NavigationPolygon 按钮。

    ../../_images/nav_2d_min_setup_step2.png

    备注

    导航网格定义的是角色的中心点所能够站立和移动的区域。请在导航多边形的边缘和碰撞对象之间留下足够的边距,这样角色跟随路径移动时就不会因为碰撞而卡住。

  4. 在场景中添加一个 CharacterBody2D 节点,设置基础的碰撞形状,添加一个精灵或网格方便观察。

  5. 在该角色节点下添加一个 NavigationAgent2D 节点。

    ../../_images/nav_2d_min_setup_step3.webp
  6. 为 CharacterBody3D 节点添加下面的脚本。场景完全加载后,我们确保设置移动目标,NavigationServer 有时间进行同步。

extends CharacterBody2D

var movement_speed: float = 200.0
var movement_target_position: Vector2 = Vector2(60.0,180.0)

@onready var navigation_agent: NavigationAgent2D = $NavigationAgent2D

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

    # 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: Vector2):
    navigation_agent.target_position = movement_target

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

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

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

备注

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