使用 NavigationPathQueryObject
小訣竅
路徑查詢參數提供多種選項,以提升路徑尋找效能或降低記憶體使用量。
這些參數可滿足高階節點不一定能涵蓋的進階路徑尋找需求。
請參閱下方各選項對應的章節。
NavigationPathQueryObjects 可與 NavigationServer.query_path() 搭配使用, 以取得高度 自訂 的導覽路徑, 並可選擇性地包含關於該路徑的 中繼資料 (metadata) 。
與獲取正常的 NavigationPath 相比,這需要更多的設定,但可以讓您根據專案的不同需求定制尋路並提供路徑資料。
NavigationPathQueryObjects 由一對物件組成:NavigationPathQueryParameters 物件用於保存查詢的自訂選項;NavigationPathQueryResult 則用於接收查詢產生的路徑與中繼資料(定期)更新。
2D 與 3D 的 NavigationPathQueryParameters 分別有 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
路徑後處理選項
依導覽網格多邊形配置而異的路徑後處理差異。
路徑查詢會從距離最近的導覽網格多邊形邊緣,沿可用多邊形移動至另一個最近邊緣;若可行,會建立通往目標位置多邊形的多邊形走廊(corridor)。
這條原始的「搜尋」多邊形走廊路徑通常未經良好最佳化,並不適合代理直接行走。例如,在較大的多邊形上,落在最近邊緣點可能導致繞遠路。為改善查詢返回的路徑品質,可使用各種 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 中哪個物件或節點擁有者。
例如判斷某路徑點是否為需要腳本接管的導覽連結起點或終點。
在最基本的路徑用途下,並非總需要中繼資料。可選擇性地停用中繼資料收集,以取得部分效能與記憶體優勢。
排除或包含區域
小訣竅
在大型、以區域分割的導覽地圖上,區域篩選可大幅改善效能。
查詢參數可將路徑尋找限制於特定區域的導覽網格。
若大型導覽地圖被良好地分割為較小區域,可在搜尋初期就略過大量多邊形,對效能有很大幫助。
在預設情況或未設定時,查詢的導覽地圖中所有區域都會被納入。
若在
excluded_regions陣列加入某區域的 RID,該區域的導覽網格將在路徑搜尋中被忽略。若在
included_regions陣列加入某區域的 RID,路徑搜尋僅會考慮該區域(與其他被加入者)的導覽網格,未被加入的其他區域將被忽略。若某區域同時被列入包含與排除,將以排除為準。
當區域分塊以格線對齊時,區域篩選的效能效果最佳。如此一來即可只包含起點所在分塊與其周邊分塊,而非整張導覽地圖。
即使目標在這些周邊分塊之外(可隨時增加更多「環」),路徑尋找仍會嘗試建立通往最接近目標的多邊形的路徑。這通常會產生朝正確方向前進的「半路徑」,且成本僅為全地圖搜尋的一小部分。
下列對基本路徑查詢腳本的補充,示範如何將區域分塊對應與區域篩選結合。這不是完整可執行的範例。
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屬性可在已搜尋的多邊形數超過此值時停止搜尋。
當搜尋因達到限制而停止時,會重設並建立一條從起點所屬多邊形到目前為止最接近目標位置的多邊形的路徑。
警告
雖有助於效能,但若搜尋限制設得過低,會嚴重影響路徑品質。依多邊形配置與搜尋模式不同,回傳的路徑可能完全偏離目標方向。