С использованием NavigationServer
2D и 3D версии NavigationServer доступны как NavigationServer2D и NavigationServer3D соответственно.
Связь с NavigationServer
Работать с NavigationServer означает подготавливать параметры для запроса, который можно отправить NavigationServer для обновления или запроса данных.
Для ссылки на внутренние объекты NavigationServer, такие как карты, регионы и агенты, используются RID-ы в качестве идентификационных номеров. Каждый узел в дереве сцены, связанный с навигацией, имеет функцию, возвращающую RID этого узла.
Потоки и Синхронизация
NavigationServer не обновляет каждое изменение немедленно, а ждет окончания кадра физики, чтобы синхронизировать все изменения вместе.
Для применения изменений ко всем картам, регионам и агентам требуется ожидание синхронизации. Синхронизация необходима, поскольку некоторые обновления, такие как перерасчёт всей навигационной карты, требуют очень больших затрат и обновления данных со всех остальных объектов. Кроме того, NavigationServer по умолчанию использует threadpool (пул потоков) для некоторых функций, например, для расчёта маршрутов избегания между агентами.
Ожидание не требуется для большинства функций get(), которые только запрашивают данные у NavigationServer, не внося изменений. Обратите внимание, что не все данные будут учитывать изменения, внесенные в том же кадре. Например, если агент избегания изменил навигационную карту в этом кадре, функция agent_get_map() всё равно вернет старую карту до синхронизации. Исключением являются узлы, которые сохраняют свои значения внутри себя перед отправкой обновления на NavigationServer. Когда геттер на узле используется для значения, обновлённого в том же кадре, он вернёт уже обновлённое значение, сохранённое в узле.
NavigationServer является thread-safe (потокобезопасным), поскольку он помещает все вызовы API, требующие внесения изменений, в очередь для выполнения на этапе синхронизации. Синхронизация NavigationServer происходит в середине физического кадра после завершения ввода данных сцены из скриптов и узлов.
Примечание
Важно отметить, что большинство изменений NavigationServer вступают в силу не сразу, а после следующего кадра физики. Это включает в себя все изменения, вносимые узлами навигации в дереве сцены или скриптами.
Примечание
Все функции установки и удаления требуют синхронизации.
Различия между 2D и 3D NavigationServer
NavigationServer2D и NavigationServer3D эквивалентны по функциональности для своего измерения.
Технически возможно использовать инструменты для создания навигационных сеток в одном измерении для другого измерения, например, запекание 2D-навигационной сетки с помощью 3D NavigationMesh при использовании плоской 3D-исходной геометрии или создание 3D-плоских навигационных сеток с помощью инструментов рисования контура полигона NavigationRegion2D и NavigationPolygons.
Ожидание синхронизации
В начале игры, при появлении новой сцены или изменении процедурной навигации любой запрос пути к NavigationServer вернет пустой или неверный результат.
На этом этапе навигационная карта всё ещё пуста или не обновлена. Все узлы дерева сцены должны сначала загрузить свои навигационные данные на NavigationServer. Каждая добавленная или изменённая карта, регион или агент должны быть зарегистрированы на NavigationServer. После этого NavigationServer потребуется физический фрейм для синхронизации и обновления карт, регионов и агентов.
Одним из обходных путей является отложенный вызов пользовательской функции настройки (чтобы все узлы были готовы). Функция настройки вносит все изменения в навигацию, например, добавляет процедурные элементы. После этого функция ожидает следующего физического кадра, прежде чем продолжить запросы пути.
extends Node3D
func _ready():
# Use call deferred to make sure the entire scene tree nodes are setup
# else await on 'physics_frame' in a _ready() might get stuck.
custom_setup.call_deferred()
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, Transform3D())
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 navigation server.
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)
using Godot;
public partial class MyNode3D : Node3D
{
public override void _Ready()
{
// Use call deferred to make sure the entire scene tree nodes are setup
// else await on 'physics_frame' in a _Ready() might get stuck.
CallDeferred(MethodName.CustomSetup);
}
private async void CustomSetup()
{
// Create a new navigation map.
Rid map = NavigationServer3D.MapCreate();
NavigationServer3D.MapSetUp(map, Vector3.Up);
NavigationServer3D.MapSetActive(map, true);
// Create a new navigation region and add it to the map.
Rid region = NavigationServer3D.RegionCreate();
NavigationServer3D.RegionSetTransform(region, Transform3D.Identity);
NavigationServer3D.RegionSetMap(region, map);
// Create a procedural navigation mesh for the region.
var newNavigationMesh = new NavigationMesh()
{
Vertices =
[
new Vector3(0.0f, 0.0f, 0.0f),
new Vector3(9.0f, 0.0f, 0.0f),
new Vector3(0.0f, 0.0f, 9.0f),
],
};
int[] polygon = [0, 1, 2];
newNavigationMesh.AddPolygon(polygon);
NavigationServer3D.RegionSetNavigationMesh(region, newNavigationMesh);
// Wait for NavigationServer sync to adapt to made changes.
await ToSignal(GetTree(), SceneTree.SignalName.PhysicsFrame);
// Query the path from the navigation server.
var startPosition = new Vector3(0.1f, 0.0f, 0.1f);
var targetPosition = new Vector3(1.0f, 0.0f, 1.0f);
Vector3[] path = NavigationServer3D.MapGetPath(map, startPosition, targetPosition, optimize: true);
GD.Print("Found a path!");
GD.Print((Variant)path);
}
}
Обратные вызовы для избегания сервера
Если агенты избегания RVO зарегистрированы для обратных вызовов избегания, NavigationServer отправляет свои сигналы velocity_computed непосредственно перед синхронизацией PhysicsServer.
Чтобы узнать больше о NavigationAgents, см. Использование NavigationAgents.
Упрощенный порядок выполнения для NavigationAgents, использующих избегание:
начинается физический кадр.
_physics_process(delta).Свойство
velocityзадается в узле NavigationAgent.Агент отправляет скорость и положение на NavigationServer.
NavigationServer ожидает синхронизации.
NavigationServer синхронизирует и вычисляет скорости избегания для всех зарегистрированных агентов избегания.
NavigationServer отправляет безопасный вектор скорости с сигналами для каждого зарегистрированного агента избегания.
Агенты получают сигнал и перемещают своего родителя, например, с помощью
move_and_slideилиlinear_velocity.PhysicsServer синхронизируется.
Физический кадр заканчивается.
Таким образом, перемещение субъекта physicsbody в функции обратного вызова с безопасной скоростью является абсолютно потоко- и физико-безопасным, поскольку все происходит внутри одного и того же физического кадра до того, как PhysicsServer зафиксирует изменения и выполнит собственные вычисления.