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.
Checking the stable version of the documentation...
使用 NavigationPathQueryObjects
小技巧
路径查询参数提供了多种选项,可用于提升寻路性能或降低内存消耗。
这些参数能够满足更高级的寻路需求,而高层节点有时无法完全覆盖这些需求。
具体选项详见下文对应章节。
NavigationPathQueryObjects 可以与 NavigationServer.query_path() 配合使用,以获取高度自定义的导航路径,包括可选的路径元数据。
与获取普通的 NavigationPath 相比,这需要更多的设置,但可以让你根据项目的不同需求来定制寻路功能和提供的路径数据。
NavigationPathQueryObjects 由一对对象组成:一个是 NavigationPathQueryParameters 对象,用于保存查询的自定义选项;另一个是 NavigationPathQueryResult,用于接收查询结果路径和元数据的(定期)更新。
NavigationPathQueryParameters 的 2D 和 3D 版本分别为 NavigationPathQueryParameters2D 和 NavigationPathQueryParameters3D。
NavigationPathQueryResult 的 2D 和 3D 版本分别为 NavigationPathQueryResult2D 和 NavigationPathQueryResult3D。
创建基础路径查询
参数和结果对象需要结对与 NavigationServer.query_path() 函数一同使用。
有关可用的自定义选项,详见下文。另请参阅类参考中每个参数的说明。
虽然这不是严格要求,但建议提前一次性创建这两个对象,将其存储在代理的持久变量中,并在每次后续路径查询时通过更新参数来重复使用。
频繁创建对象或分配内存时,复用相同对象可以提升性能。
以下脚本创建对象并提供 query_path() 函数来生成新导航路径。结果路径与使用 NavigationServer.map_get_path() 生成的路径相同,但实现了对象复用。
extends Node2D
# Prepare query objects.
var query_parameters := NavigationPathQueryParameters2D.new()
var query_result := NavigationPathQueryResult2D.new()
func query_path(p_start_position: Vector2, p_target_position: Vector2, p_navigation_layers: int = 1) -> PackedVector2Array:
if not is_inside_tree():
return PackedVector2Array()
var map: RID = get_world_2d().get_navigation_map()
if NavigationServer2D.map_get_iteration_id(map) == 0:
# This map has never synced and is empty, no point in querying it.
return PackedVector2Array()
query_parameters.map = map
query_parameters.start_position = p_start_position
query_parameters.target_position = p_target_position
query_parameters.navigation_layers = p_navigation_layers
NavigationServer2D.query_path(query_parameters, query_result)
var path: PackedVector2Array = query_result.get_path()
return path
extends Node3D
# Prepare query objects.
var query_parameters := NavigationPathQueryParameters3D.new()
var query_result := NavigationPathQueryResult3D.new()
func query_path(p_start_position: Vector3, p_target_position: Vector3, p_navigation_layers: int = 1) -> PackedVector3Array:
if not is_inside_tree():
return PackedVector3Array()
var map: RID = get_world_3d().get_navigation_map()
if NavigationServer3D.map_get_iteration_id(map) == 0:
# This map has never synced and is empty, no point in querying it.
return PackedVector3Array()
query_parameters.map = map
query_parameters.start_position = p_start_position
query_parameters.target_position = p_target_position
query_parameters.navigation_layers = p_navigation_layers
NavigationServer3D.query_path(query_parameters, query_result)
var path: PackedVector3Array = query_result.get_path()
return path
路径后处理选项
导航网格多边形布局导致的路径后处理差异。
路径查询搜索从最近的导航网格多边形边缘出发,沿着可用的多边形向最近的边缘移动。如果可能,它会构建一条通向目标位置多边形的多边形走廊。
这种原始的“搜索”多边形走廊路径优化不足,通常不适合智能体沿其移动。例如,在较大的多边形上,导航网格多边形上的最近边缘点可能导致智能体大幅绕行。为了提升查询返回路径的质量,系统提供了多种 path_postprocessing 选项。
PATH_POSTPROCESSING_CORRIDORFUNNEL后处理通过在可用的多边形走廊内的角落处收紧路径来缩短路径。这是默认的后处理方法,通常也是最实用的,因为它能够提供在可用的多边形走廊内的最短路径结果。如果多边形走廊本身已不理想(例如由于导航网格布局欠佳),收紧路径时可能会吸附到意外的多边形角落,导致绕行。
PATH_POSTPROCESSING_EDGECENTERED后处理强制将所有路径点放置在在可用的多边形走廊内被穿过的多边形边缘的中点。这种后处理通常仅在配合严格的图块式导航网格多边形使用时才有用,这些多边形大小均匀,且预期的路径跟随也受限于单元格中心,例如典型的网格游戏(移动被限制在网格单元格中心)。
PATH_POSTPROCESSING_NONE后处理按照寻路在可用的多边形走廊内实际经过的路径原样返回。这种后处理对调试非常有用,因为它能够显示路径搜索如何从最近的边缘点移动到最近的边缘点,以及选择了哪些多边形。许多意外或次优的路径结果可以通过查看此原始路径和多边形走廊立即得到解释。
路径简化
小技巧
路径简化可以帮助代理转向,或解决代理在狭窄多边形边缘抖动的问题。
有无路径简化的路径点差异。
启用 simplify_path 后,会对路径应用 Ramer-Douglas-Peucker 路径简化算法的变体。该算法通过根据所使用的 simplify_epsilon 移除不太相关的路径点来拉直路径。
路径简化有助于解决“开阔地带”中因存在许多不必要的多边形边缘而导致的各种代理移动问题。例如,当地形网格烘焙为导航网格时,地形中那些微小(但对寻路几乎毫无意义)的高度变化可能会导致多边形数量过多。
路径简化还有助于代理“转向”,因为它们只需要瞄准更关键的拐角路径点。
警告
路径简化是对路径的额外最终后处理步骤。它会增加查询的性能开销,因此仅在真正需要时才启用。
备注
路径简化在 NavigationServer 中作为通用函数公开。它也可以在导航查询之外用于各种类型的位置数组。
路径元数据
小技巧
禁用不需要的路径元数据选项可以提升性能并降低内存消耗。
路径查询可以为每个路径点返回额外的元数据。
PATH_METADATA_INCLUDE_TYPES标志收集一个数组,其中包含有关点所有者的基本信息,例如点属于区域还是链接。PATH_METADATA_INCLUDE_RIDS标志收集一个数组,其中包含点所有者的 RID。根据点所有者的基本类型,这些 RID 可用于 NavigationServer 中与区域或链接相关的各种函数。PATH_METADATA_INCLUDE_OWNERS标志收集一个数组,其中包含点所有者的ObjectID。这些对象 ID 可以与 @GlobalScope.instance_from_id() 一起使用,以检索该对象实例背后的节点,例如 NavigationRegion 或 NavigationLink 节点。
默认情况下会收集所有路径元数据,因为这些元数据对于更高级的导航玩法可能至关重要。
例如,了解路径点对应于 SceneTree 中的哪个对象或节点所有者。
例如,了解路径点是否为导航链接的起点或终点,需要脚本接管。
对于最基本的路径使用,元数据并不总是必需的。可以选择性地禁用路径元数据收集,以获得一定的性能提升并减少内存消耗。
排除或包含区域
小技巧
区域过滤器可以极大地提升分区大型导航地图的性能。
查询参数允许将寻路限制在特定区域的导航网格内。
如果大型导航地图被很好地分区为较小的区域,这可以极大地提升性能,因为查询可以在路径搜索的最早检查之一中跳过大量多边形。
默认情况下(即如果留空),所查询导航地图的所有区域都将包含。
如果将区域的 RID 添加到
excluded_regions数组中,该区域的导航网格将在路径搜索中被忽略。如果将区域的 RID 添加到
included_regions数组中,该区域的导航网格将在路径搜索中被考虑,同时所有其他未被包含的区域也将被忽略。如果某个区域同时被包含和排除,则被视为已排除。
当与按网格对齐的导航区域块配合使用时,区域过滤器在性能方面非常有效。这样,过滤器可以设置为仅包含起始位置块和周围的块,而不是整个导航地图。
即使目标可能在这些周围块之外(总可以添加更多的“环”),寻路仍会尝试创建一条通向最接近目标的多边形的路径。这通常会创建朝向大致方向的半路径,这些路径已经足够好,而性能开销仅为全地图搜索的一小部分。
以下对基础路径查询脚本的补充展示了将区域块映射与区域过滤器集成的思路。这不是一个完整的可用示例。
extends Node2D
# ...
var chunk_id_to_region_rid: Dictionary[Vector2i, RID] = {}
func query_path(p_start_position: Vector2, p_target_position: Vector2, p_navigation_layers: int = 1) -> PackedVector2Array:
# ...
var regions_around_start_position: Array[RID] = []
var chunk_rings: int = 1 # Increase for very small regions or more quality.
var start_chunk_id: Vector2i = floor(p_start_position / float(chunk_size))
for y: int in range(start_chunk_id.y - chunk_rings, start_chunk_id.y + chunk_rings):
for x: int in range(start_chunk_id.x - chunk_rings, start_chunk_id.x + chunk_rings):
var chunk_id: Vector2i = Vector2i(x, y)
if chunk_id_to_region_rid.has(chunk_id):
var region: RID = chunk_id_to_region_rid[chunk_id]
regions_around_start_position.push_back(region)
query_parameters.included_regions = regions_around_start_position
# ...
extends Node3D
# ...
var chunk_id_to_region_rid: Dictionary[Vector3i, RID] = {}
func query_path(p_start_position: Vector3, p_target_position: Vector3, p_navigation_layers: int = 1) -> PackedVector3Array:
# ...
var regions_around_start_position: Array[RID] = []
var chunk_rings: int = 1 # Increase for very small regions or more quality.
var start_chunk_id: Vector3i = floor(p_start_position / float(chunk_size))
var y: int = 0 # Assume a planar navigation map for simplicity.
for z: int in range(start_chunk_id.z - chunk_rings, start_chunk_id.z + chunk_rings):
for x: int in range(start_chunk_id.x - chunk_rings, start_chunk_id.x + chunk_rings):
var chunk_id: Vector3i = Vector3i(x, y, z)
if chunk_id_to_region_rid.has(chunk_id):
var region: RID = chunk_id_to_region_rid[chunk_id]
regions_around_start_position.push_back(region)
query_parameters.included_regions = regions_around_start_position
# ...
路径裁剪与限制
小技巧
合理设置的限制可以极大地提升大型导航地图的性能,尤其是当目标最终无法到达时。
将返回的路径裁剪到特定距离。
查询参数允许将返回的路径裁剪到特定长度。这些选项将路径裁剪作为后处理的一部分。路径仍然按照完整长度进行搜索,因此质量相同。路径长度裁剪有助于创建更适合受限游戏玩法的路径,例如具有有限移动范围的战术游戏。
path_return_max_length属性可用于将返回的路径裁剪到特定的最大长度。path_return_max_radius属性可用于将返回的路径裁剪到起始位置周围圆形(2D)或球形(3D)半径内。
查询参数允许将路径搜索限制为最多搜索到特定距离或特定数量的多边形。这些选项用于性能优化,并直接影响路径搜索。
path_search_max_distance属性可用于在从起始位置出发超过此距离时停止路径搜索。path_search_max_polygons属性可用于在超过此搜索过的多边形数量时停止路径搜索。
当路径搜索因达到限制而停止时,路径会重置并创建一条从起始位置多边形到目前找到的最接近目标位置的多边形的路径。
警告
虽然有利于性能,但如果路径搜索限制值设置得过低,可能会对路径质量产生非常严重的负面影响。根据多边形布局和搜索模式,返回的路径可能会走向完全错误的方向,而不是目标的方向。