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...
Usando o NavigationServer
As versões 2D e 3D do NavigationServer estão disponíveis como NavigationServer2D e NavigationServer3D, respectivamente.
Comunicando-se com o NavigationServer
Trabalhar com o NavigationServer significa preparar parâmetros para uma consulta (query) que pode ser enviada ao NavigationServer para atualizações ou para solicitar dados.
Para referenciar os objetos internos do NavigationServer, como mapas, regiões e agentes, RIDs são usados como números de identificação. Cada nó relacionado à navegação na árvore de cenas possui uma função que retorna o RID para este nó.
Multithreading e Sincronização
O NavigationServer não atualiza cada alteração imediatamente, mas espera até o final do quadro de física (physics frame) para sincronizar todas as alterações juntas.
Esperar pela sincronização é necessário para aplicar as alterações a todos os mapas, regiões e agentes. A sincronização é feita porque algumas atualizações, como o recálculo de todo o mapa de navegação, são muito caras e exigem dados atualizados de todos os outros objetos. Além disso, o NavigationServer usa um threadpool por padrão para algumas funcionalidades, como o cálculo de desvio entre agentes.
A espera não é necessária para a maioria das funções get() que apenas solicitam dados do NavigationServer sem fazer alterações. Note que nem todos os dados contabilizarão as alterações feitas no mesmo quadro. Por exemplo, se um agente de desvio alterou o mapa de navegação neste quadro, a função agent_get_map() ainda retornará o mapa antigo antes da sincronização. A exceção a isso são os nós que armazenam seus valores internamente antes de enviar a atualização para o NavigationServer. Quando um getter em um nó é usado para um valor que foi atualizado no mesmo quadro, ele retornará o valor já atualizado armazenado no nó.
O NavigationServer é thread-safe (seguro para threads), pois coloca todas as chamadas de API que desejam fazer alterações em uma fila para serem executadas na fase de sincronização. A sincronização para o NavigationServer acontece no meio do quadro de física, depois que as entradas de cena dos scripts e nós já foram todas processadas.
Nota
A lição importante é que a maioria das alterações no NavigationServer entra em vigor após o próximo quadro de física e não imediatamente. Isso inclui todas as alterações feitas por nós relacionados à navegação na árvore de cenas ou por meio de scripts.
Nota
Todos os setters e funções de exclusão exigem sincronização.
Diferenças entre o NavigationServer 2D e 3D
O NavigationServer2D e o NavigationServer3D são equivalentes em funcionalidade para suas respectivas dimensões.
Tecnicamente, é possível usar as ferramentas de criação de malhas de navegação de uma dimensão para a outra dimensão, por exemplo, gerar uma malha de navegação 2D com o NavigationMesh 3D ao usar geometria de origem 3D plana, ou criar malhas de navegação 3D planas com as ferramentas de desenho de contorno de polígono do NavigationRegion2D e NavigationPolygons.
Aguardando pela sincronização
No início do jogo, em uma nova cena ou em mudanças procedurais de navegação, qualquer consulta de caminho para um NavigationServer retornará vazia ou incorreta.
O mapa de navegação ainda está vazio ou desatualizado neste ponto. Todos os nós da árvore de cenas precisam primeiro enviar seus dados relacionados à navegação para o NavigationServer. Cada mapa, região ou agente adicionado ou alterado precisa ser registrado no NavigationServer. Posteriormente, o NavigationServer exige um quadro de física para sincronização para atualizar os mapas, regiões e agentes.
Uma solução alternativa é fazer uma chamada adiada (deferred call) para uma função de configuração personalizada (para que todos os nós estejam prontos). A função de configuração faz todas as alterações de navegação, por exemplo, adicionando elementos procedurais. Depois disso, a função espera pelo próximo quadro de física antes de continuar com as consultas de caminho.
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);
}
}
Callbacks de desvio do servidor (Server Avoidance Callbacks)
Se os agentes de desvio RVO estiverem registrados para callbacks de desvio, o NavigationServer despacha seus sinais velocity_computed logo antes da sincronização do PhysicsServer.
Para saber mais sobre NavigationAgents veja Usando NavigationAgents.
A ordem simplificada de execução para NavigationAgents que usam desvio:
o quadro de física começa.
_physics_process(delta).A propriedade
velocityé definida no Nó NavigationAgent.O agente envia a velocidade e a posição para o NavigationServer.
O NavigationServer aguarda a sincronização.
O NavigationServer sincroniza e calcula as velocidades de desvio para todos os agentes de desvio registrados.
O NavigationServer envia o vetor de velocidade segura com sinais para cada um dos agentes de desvio registrados.
Os agentes recebem o sinal e movem seu pai, por exemplo, com
move_and_slideoulinear_velocity.O PhysicsServer sincroniza.
O quadro de física termina.
Portanto, mover um ator do tipo corpo de física (physicsbody) na função de callback com a velocidade segura é perfeitamente seguro para threads e física, pois tudo acontece dentro do mesmo quadro de física antes que o PhysicsServer consolide as alterações e faça seus próprios cálculos.