Introdução

O Godot 4 tem um sistema de navegação robusto construído sobre NavigationServer2D/3D, nodes NavigationRegion e nodes NavigationAgent. É uma melhoria significativa em relação ao sistema do Godot 3 — mais flexível, mais performático e muito mais fácil de configurar para cenários complexos, como obstáculos dinâmicos e múltiplos tipos de agentes.

Visão geral rápida: NavigationRegion define onde os agentes podem andar. NavigationAgent cuida do pathfinding de cada entidade individual. NavigationServer gerencia tudo nos bastidores.

Conceitos fundamentais

  • NavigationRegion2D / NavigationRegion3D — Define uma área caminhável usando um NavigationPolygon (2D) ou NavigationMesh (3D). Você pode ter várias regiões; o servidor as mescla automaticamente.
  • NavigationAgent2D / NavigationAgent3D — Anexe a um CharacterBody para cuidar do pathfinding. Calcula a próxima posição do caminho, trata o avoidance e emite sinais quando a navegação termina.
  • NavigationServer2D / NavigationServer3D — Singleton que gerencia todos os dados de navegação. Você raramente interage diretamente com ele, mas ele cuida das atualizações de mapa, das consultas de caminho e das conexões entre regiões.

Configuração da navegação 2D

Passo 1: adicionar o NavigationRegion2D

  1. Adicione um node NavigationRegion2D à sua cena.
  2. No Inspector, crie um novo recurso NavigationPolygon.
  3. Desenhe o polígono caminhável no editor 2D. O polígono define onde os agentes podem andar.

Integração com TileMap: o NavigationPolygon pode ser gerado (baked) a partir de dados do TileMap. Se os seus tiles têm polígonos de navegação definidos no TileSet, basta adicionar um NavigationRegion2D e fazer o bake — ele gerará automaticamente a área caminhável a partir do layout dos seus tiles.

Adicionando um NavigationAgent2D

Adicione um NavigationAgent2D como filho do seu CharacterBody2D. Aqui está um script de movimento completo:

extends CharacterBody2D

@export var speed: float = 200.0
@onready var nav_agent: NavigationAgent2D = $NavigationAgent2D

func _ready() -> void:
    # Wait for the navigation map to be ready
    await get_tree().physics_frame

    nav_agent.path_desired_distance = 4.0
    nav_agent.target_desired_distance = 4.0

func set_target(target_pos: Vector2) -> void:
    nav_agent.target_position = target_pos

func _physics_process(delta: float) -> void:
    if nav_agent.is_navigation_finished():
        return

    var next_pos := nav_agent.get_next_path_position()
    var direction := global_position.direction_to(next_pos)
    velocity = direction * speed
    move_and_slide()

Importante: o NavigationServer precisa de um frame de física para sincronizar. Sempre faça await get_tree().physics_frame antes de definir a primeira posição de alvo em _ready(), ou o agente pode não encontrar um caminho válido.

Principais propriedades do NavigationAgent2D

  • target_position — Para onde o agente quer ir
  • path_desired_distance — Quão perto o agente precisa estar de cada ponto do caminho para avançar ao próximo
  • target_desired_distance — Quão perto o agente precisa estar do alvo para considerar a navegação concluída
  • max_speed — Usado nos cálculos de avoidance (não limita o seu código de movimento)

Navegação 3D

O sistema de navegação 3D funciona de forma idêntica ao 2D, apenas com tipos de node diferentes:

  • NavigationRegion3D + NavigationMesh
  • NavigationAgent3D
  • NavigationServer3D
extends CharacterBody3D

@export var speed: float = 5.0
@onready var nav_agent: NavigationAgent3D = $NavigationAgent3D

func _ready() -> void:
    await get_tree().physics_frame
    nav_agent.path_desired_distance = 0.5
    nav_agent.target_desired_distance = 0.5

func set_target(target_pos: Vector3) -> void:
    nav_agent.target_position = target_pos

func _physics_process(delta: float) -> void:
    if nav_agent.is_navigation_finished():
        return

    var next_pos := nav_agent.get_next_path_position()
    var direction := global_position.direction_to(next_pos)
    velocity = direction * speed
    velocity.y -= 9.8 * delta  # Apply gravity
    move_and_slide()

Bake de navigation meshes

No 3D, você normalmente gera (bake) a navigation mesh a partir da geometria do seu nível, em vez de desenhá-la manualmente.

Configuração do bake

Propriedades importantes no recurso NavigationMesh:

  • Agent Radius — Quão longe os agentes ficam das paredes. Valores maiores criam caminhos mais conservadores.
  • Agent Height — Altura mínima de teto para áreas caminháveis.
  • Agent Max Climb — Altura máxima de degrau que os agentes conseguem subir (ex.: escadas).
  • Agent Max Slope — Ângulo máximo de inclinação caminhável, em graus.
# Bake navigation mesh at runtime
var nav_region: NavigationRegion3D = $NavigationRegion3D
nav_region.bake_navigation_mesh()

# Wait for baking to complete
await nav_region.bake_finished
print("Navigation mesh baked!")

Camadas de navegação

As camadas de navegação permitem separar áreas caminháveis para diferentes tipos de agentes. Por exemplo, unidades terrestres e unidades voadoras podem ter navigation meshes diferentes.

# Set navigation layers on the agent
nav_agent.set_navigation_layer_value(1, true)   # Ground layer
nav_agent.set_navigation_layer_value(2, false)  # Not flying layer

# Set navigation layers on the region
nav_region.set_navigation_layer_value(1, true)   # This region is for ground
nav_region.set_navigation_layer_value(2, false)  # Not for flying

Uma configuração típica de camadas:

  • Camada 1 — Unidades terrestres (soldados, veículos)
  • Camada 2 — Unidades voadoras (drones, pássaros)
  • Camada 3 — Unidades grandes (apenas corredores largos)

Avoidance

O NavigationAgent tem avoidance local embutido para evitar que os agentes se sobreponham. Quando o avoidance está ativado, o agente calcula uma velocidade segura que o afasta dos outros agentes.

extends CharacterBody2D

@export var speed: float = 200.0
@onready var nav_agent: NavigationAgent2D = $NavigationAgent2D

func _ready() -> void:
    await get_tree().physics_frame
    nav_agent.avoidance_enabled = true
    nav_agent.radius = 20.0
    nav_agent.max_speed = speed
    nav_agent.velocity_computed.connect(_on_velocity_computed)

func set_target(target_pos: Vector2) -> void:
    nav_agent.target_position = target_pos

func _physics_process(delta: float) -> void:
    if nav_agent.is_navigation_finished():
        return

    var next_pos := nav_agent.get_next_path_position()
    var direction := global_position.direction_to(next_pos)
    # Set the desired velocity — the agent will compute a safe one
    nav_agent.velocity = direction * speed

func _on_velocity_computed(safe_velocity: Vector2) -> void:
    velocity = safe_velocity
    move_and_slide()

Como funciona: em vez de usar a velocidade diretamente, você define nav_agent.velocity como a sua velocidade desejada. O agente então calcula uma safe_velocity por meio do sinal velocity_computed que evita os agentes próximos. Você aplica essa velocidade segura no callback.

Navegação dinâmica

Você pode modificar a navigation mesh em tempo de execução para lidar com portas, paredes destrutíveis ou terreno em mudança:

# Enable/disable a navigation region (e.g., open/close a door)
var door_region: NavigationRegion2D = $DoorNavigationRegion
door_region.enabled = false  # Block this path
door_region.enabled = true   # Open this path

# Add a navigation obstacle (blocks agent paths)
var obstacle := NavigationObstacle2D.new()
obstacle.radius = 30.0
add_child(obstacle)

# Re-bake after level changes (3D)
nav_region.bake_navigation_mesh()
await nav_region.bake_finished

Nota de desempenho: fazer o bake de uma navigation mesh 3D em tempo de execução é custoso. Para obstáculos dinâmicos em 3D, prefira usar NavigationObstacle3D em vez de refazer o bake. No 2D, você pode alternar NavigationRegion2D.enabled a baixo custo.

Padrões comuns

IA de inimigo seguindo o jogador

extends CharacterBody2D

@export var speed: float = 150.0
@export var chase_range: float = 300.0
@onready var nav_agent: NavigationAgent2D = $NavigationAgent2D
var player: Node2D

func _ready() -> void:
    await get_tree().physics_frame
    player = get_tree().get_first_node_in_group("player")

func _physics_process(delta: float) -> void:
    if not player:
        return

    var distance := global_position.distance_to(player.global_position)
    if distance > chase_range:
        return  # Too far, don't chase

    # Update target every frame (or throttle to every N frames)
    nav_agent.target_position = player.global_position

    if nav_agent.is_navigation_finished():
        return

    var next_pos := nav_agent.get_next_path_position()
    var direction := global_position.direction_to(next_pos)
    velocity = direction * speed
    move_and_slide()

Rotas de patrulha de NPC

extends CharacterBody2D

@export var speed: float = 100.0
@export var patrol_points: Array[Vector2] = []
@onready var nav_agent: NavigationAgent2D = $NavigationAgent2D
var current_patrol_index: int = 0

func _ready() -> void:
    await get_tree().physics_frame
    if patrol_points.size() > 0:
        nav_agent.target_position = patrol_points[0]

func _physics_process(delta: float) -> void:
    if patrol_points.size() == 0:
        return

    if nav_agent.is_navigation_finished():
        # Move to next patrol point
        current_patrol_index = (current_patrol_index + 1) % patrol_points.size()
        nav_agent.target_position = patrol_points[current_patrol_index]
        return

    var next_pos := nav_agent.get_next_path_position()
    var direction := global_position.direction_to(next_pos)
    velocity = direction * speed
    move_and_slide()

Clicar para mover (top-down)

extends CharacterBody2D

@export var speed: float = 200.0
@onready var nav_agent: NavigationAgent2D = $NavigationAgent2D

func _ready() -> void:
    await get_tree().physics_frame

func _unhandled_input(event: InputEvent) -> void:
    if event is InputEventMouseButton and event.pressed:
        nav_agent.target_position = get_global_mouse_position()

func _physics_process(delta: float) -> void:
    if nav_agent.is_navigation_finished():
        return

    var next_pos := nav_agent.get_next_path_position()
    var direction := global_position.direction_to(next_pos)
    velocity = direction * speed
    move_and_slide()

Mudanças do Godot 3 para o Godot 4

Godot 3 Godot 4
node Navigation2D Removido. Use NavigationRegion2D + NavigationAgent2D
Navigation.get_simple_path() NavigationServer2D.map_get_path()
Seguir o caminho manualmente NavigationAgent.get_next_path_position() cuida disso
Sem avoidance embutido NavigationAgent.avoidance_enabled
Sem camadas de navegação 32 camadas de navegação por mapa
Sem obstáculos NavigationObstacle2D/3D