Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

使用 NavigationServer

NavigationServer 即導覽伺服器。2D 和 3D 版本的 NavigationServer 分別為 NavigationServer2DNavigationServer3D

2D 和 3D 使用的 NavigationServer 是一樣的,NavigationServer3D 是主要伺服器。NavigationServer2D 只是一個前端,會進行 2D 位置和 3D 位置的相互轉換。因此,完全可以只用 NavigationServer3D 的 API 來實作 2D 導覽(就是會有點繁瑣)。

與其他腳本語言溝通

To work with the NavigationServer means to prepare parameters for a query that can be sent to the NavigationServer for updates or requesting data.

To reference the internal NavigationServer objects like maps, regions and agents RIDs are used as identification numbers. Every navigation related node in the scene tree has a function that returns the RID for this node.

建立內容

The NavigationServer does not update every change immediately but waits until the end of the physics frame to synchronize all the changes together.

Waiting for synchronization is required to apply changes to all maps, regions and agents. Synchronization is done because some updates like a recalculation of the entire navigation map are very expensive and require updated data from all other objects. Also the NavigationServer uses a threadpool by default for some functionality like avoidance calculation between agents.

Waiting is not required for most get() functions that only request data from the NavigationServer without making changes. Note that not all data will account for changes made in the same frame. E.g. if an avoidance agent changed the navigation map this frame the agent_get_map() function will still return the old map before the synchronization. The exception to this are nodes that store their values internally before sending the update to the NavigationServer. When a getter on a node is used for a value that was updated in the same frame it will return the already updated value stored on the node.

The NavigationServer is thread-safe as it places all API calls that want to make changes in a queue to be executed in the synchronization phase. Synchronization for the NavigationServer happens in the middle of the physics frame after scene input from scripts and nodes are all done.

備註

The important takeaway is that most NavigationServer changes take effect after the next physics frame and not immediately. This includes all changes made by navigation related nodes in the scene tree or through scripts.

以下函式只會在同步階段執行:

  • map_set_active()

  • map_set_up()

  • map_set_cell_size()

  • map_set_edge_connection_margin()

  • region_set_map()

  • region_set_transform()

  • region_set_enter_cost()

  • region_set_travel_cost()

  • region_set_navigation_layers()

  • region_set_navigation_mesh()

  • agent_set_map()

  • agent_set_neighbor_dist()

  • agent_set_max_neighbors()

  • agent_set_time_horizon()

  • agent_set_radius()

  • agent_set_max_speed()

  • agent_set_velocity()

  • agent_set_target_velocity()

  • agent_set_position()

  • agent_set_ignore_y()

  • agent_set_callback()

  • free()

2D 和 3D NavigationServer 的區別

NavigationServer2D 和 NavigationServer3D 在各自維度中的功能是等價的,底層使用的相同的 NavigationServer。

Strictly technical a NavigationServer2D is a myth. The NavigationServer2D is a frontend to facilitate conversions of Vector2(x, y) to Vector3(x, 0.0, z) and back for the NavigationServer3D API. 2D uses a flat 3D mesh pathfinding and the NavigationServer2D facilitates the conversions. When a guide uses just NavigationServer without the 2D or 3D suffix it usually works for both servers by exchange Vector2(x, y) with Vector3(x, 0.0, z) or reverse.

Technically it is possible to use the tools for creating navigation meshes in one dimension for the other dimension, e.g. baking a 2D navigation mesh with the 3D NavigationMesh when using flat 3D source geometry or creating 3D flat navigation meshes with the polygon outline draw tools of NavigationRegion2D and NavigationPolygons.

使用 NavigationServer2D API 建立的任何 RID 也適用於 NavigationServer3D API,而 2D 和 3D 迴避代理可以存在於同一地圖上。

備註

Regions created in 2D and 3D will merge their navigation meshes when placed on the same map and merge conditions apply. The NavigationServer does not discriminate between NavigationRegion2D and NavigationRegion3D nodes as both are regions on the server. By default those nodes register on different navigation maps so this merge can only happen when maps are changed manually e.g. with scripts.

啟用迴避的 Actor 在放置在同一張地圖上時將同時迴避 2D 和 3D 迴避代理。

警告

在 Godot 自訂建置上停用 3D 時,無法使用 NavigationServer2D。

撰寫自定說明文件

在遊戲開始時,新場景或程式導覽發生變化,對導覽伺服器的任何路徑查詢都將傳回空或錯誤。

The navigation map is still empty or not updated at this point. All nodes from the scene tree need to first upload their navigation related data to the NavigationServer. Each added or changed map, region or agent need to be registered with the NavigationServer. Afterward the NavigationServer requires a physics frame for synchronization to update the maps, regions and agents.

One workaround is to make a deferred call to a custom setup function (so all nodes are ready). The setup function makes all the navigation changes, e.g. adding procedural stuff. Afterwards the function waits for the next physics frame before continuing with path queries.

extends Node3D

func _ready():
    # use call deferred to make sure the entire scene tree nodes are setup
    # else await / yield on 'physics_frame' in a _ready() might get stuck
    call_deferred("custom_setup")

func custom_setup():

    # create a new navigation map
    var map: RID = NavigationServer3D.map_create()
    NavigationServer3D.map_set_up(map, Vector3.UP)
    NavigationServer3D.map_set_active(map, true)

    # create a new navigation region and add it to the map
    var region: RID = NavigationServer3D.region_create()
    NavigationServer3D.region_set_transform(region, Transform())
    NavigationServer3D.region_set_map(region, map)

    # create a procedural navigation mesh for the region
    var new_navigation_mesh: NavigationMesh = NavigationMesh.new()
    var vertices: PackedVector3Array = PackedVector3Array([
        Vector3(0,0,0),
        Vector3(9.0,0,0),
        Vector3(0,0,9.0)
    ])
    new_navigation_mesh.set_vertices(vertices)
    var polygon: PackedInt32Array = PackedInt32Array([0, 1, 2])
    new_navigation_mesh.add_polygon(polygon)
    NavigationServer3D.region_set_navigation_mesh(region, new_navigation_mesh)

    # wait for NavigationServer sync to adapt to made changes
    await get_tree().physics_frame

    # query the path from the navigationserver
    var start_position: Vector3 = Vector3(0.1, 0.0, 0.1)
    var target_position: Vector3 = Vector3(1.0, 0.0, 1.0)
    var optimize_path: bool = true

    var path: PackedVector3Array = NavigationServer3D.map_get_path(
        map,
        start_position,
        target_position,
        optimize_path
    )

    print("Found a path!")
    print(path)

伺服器迴避回調

If RVO avoidance agents are registered for avoidance callbacks the NavigationServer dispatches their velocity_computed signals just before the PhysicsServer synchronization.

GDScript 亦支援 GDScript 格式字串

使用迴避的導覽代理的簡化執行順序:

  • 物理屬性

  • _physics_process(delta).

  • set_velocity() on NavigationAgent Node.

  • 代理將速度和位置傳送到導覽伺服器。

  • 導覽伺服器等待同步。

  • 導覽伺服器同步並計算所有註冊迴避代理的迴避速度。

  • NavigationServer sends safe velocity vector with signals for each registered avoidance agents.

  • Agents receive the signal and move their parent e.g. with move_and_slide or linear_velocity.

  • 實體伺服器同步。

  • 物理框架結束。

Therefore moving a physicsbody actor in the callback function with the safe velocity is perfectly thread- and physics-safe as all happens inside the same physics frame before the PhysicsServer commits to changes and does its own calculations.