Введение

В Godot 4 реализована надёжная система навигации, построенная на NavigationServer2D/3D, нодах NavigationRegion и нодах NavigationAgent. Это значительный шаг вперёд по сравнению с системой Godot 3 — она более гибкая, более производительная и намного проще в настройке для сложных сценариев вроде динамических препятствий и нескольких типов агентов.

Краткий обзор: NavigationRegion определяет, где могут ходить агенты. NavigationAgent отвечает за поиск пути для отдельных сущностей. NavigationServer управляет всем за кулисами.

Основные концепции

  • NavigationRegion2D / NavigationRegion3D — Определяет проходимую область с помощью NavigationPolygon (2D) или NavigationMesh (3D). Регионов может быть несколько; сервер объединяет их автоматически.
  • NavigationAgent2D / NavigationAgent3D — Прикрепляется к CharacterBody для обработки поиска пути. Вычисляет следующую позицию пути, обрабатывает уклонение и посылает сигналы по завершении навигации.
  • NavigationServer2D / NavigationServer3D — Синглтон, который управляет всеми навигационными данными. С ним редко взаимодействуют напрямую, но он обрабатывает обновления карты, запросы путей и соединения регионов.

Настройка 2D-навигации

Шаг 1: Добавьте NavigationRegion2D

  1. Добавьте в сцену ноду NavigationRegion2D.
  2. В инспекторе создайте новый ресурс NavigationPolygon.
  3. Нарисуйте проходимый полигон в 2D-редакторе. Полигон определяет, где агенты могут ходить.

Интеграция с TileMap: NavigationPolygon можно запечь из данных TileMap. Если у ваших тайлов в TileSet определены навигационные полигоны, просто добавьте NavigationRegion2D и запеките — проходимая область будет автоматически сгенерирована из раскладки тайлов.

Добавление NavigationAgent2D

Добавьте NavigationAgent2D как дочернюю ноду вашего CharacterBody2D. Вот полный скрипт движения:

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()

Важно: NavigationServer нужен один кадр физики для синхронизации. Всегда выполняйте await get_tree().physics_frame перед установкой первой целевой позиции в _ready(), иначе агент может не найти корректный путь.

Ключевые свойства NavigationAgent2D

  • target_position — Куда агент хочет двигаться
  • path_desired_distance — Насколько близко агент должен быть к каждой точке пути, чтобы перейти к следующей
  • target_desired_distance — Насколько близко агент должен быть к цели, чтобы навигация считалась завершённой
  • max_speed — Используется для расчётов уклонения (не ограничивает ваш код движения)

3D-навигация

Система 3D-навигации работает так же, как и 2D, только с другими типами нод:

  • 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()

Запекание навигационных мешей

В 3D навигационный меш обычно запекается из геометрии уровня, а не рисуется вручную.

Настройка запекания

Ключевые свойства ресурса NavigationMesh:

  • Agent Radius — Насколько далеко агенты держатся от стен. Большие значения создают более консервативные пути.
  • Agent Height — Минимальная высота потолка для проходимых областей.
  • Agent Max Climb — Максимальная высота ступеньки, на которую агенты могут подниматься (например, лестницы).
  • Agent Max Slope — Максимальный проходимый угол наклона в градусах.
# 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!")

Слои навигации

Слои навигации позволяют разделять проходимые области для разных типов агентов. Например, наземные и летающие юниты могут иметь разные навигационные меши.

# 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

Типичная конфигурация слоёв:

  • Layer 1 — Наземные юниты (солдаты, транспорт)
  • Layer 2 — Летающие юниты (дроны, птицы)
  • Layer 3 — Крупные юниты (только широкие коридоры)

Уклонение (Avoidance)

NavigationAgent имеет встроенное локальное уклонение, чтобы агенты не перекрывали друг друга. Когда уклонение включено, агент вычисляет безопасную скорость, уводящую его от других агентов.

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()

Как это работает: Вместо того чтобы использовать скорость напрямую, вы задаёте nav_agent.velocity равной желаемой скорости. Затем агент через сигнал velocity_computed вычисляет safe_velocity, уклоняющуюся от близких агентов. Эту безопасную скорость вы применяете в колбэке.

Динамическая навигация

Вы можете изменять навигационный меш во время выполнения, чтобы обрабатывать двери, разрушаемые стены или меняющийся ландшафт:

# 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

Замечание о производительности: Запекание 3D-навигационного меша во время выполнения обходится дорого. Для динамических препятствий в 3D предпочтительнее использовать NavigationObstacle3D, а не повторное запекание. В 2D можно дёшево переключать NavigationRegion2D.enabled.

Распространённые паттерны

ИИ врага, преследующий игрока

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()

Маршруты патрулирования 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()

Перемещение по клику (вид сверху)

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()

Изменения от Godot 3 к Godot 4

Godot 3 Godot 4
Нода Navigation2D Удалена. Используйте NavigationRegion2D + NavigationAgent2D
Navigation.get_simple_path() NavigationServer2D.map_get_path()
Ручное следование по пути Этим занимается NavigationAgent.get_next_path_position()
Нет встроенного уклонения NavigationAgent.avoidance_enabled
Нет слоёв навигации 32 слоя навигации на карту
Нет препятствий NavigationObstacle2D/3D