Использование NavigationAgents
NavigationsAgents — это вспомогательные узлы, которые объединяют функции поиска пути, следования по пути и избегания агентов для наследующего родительского узла Node2D/3D. Они упрощают выполнение общих вызовов к API NavigationServer от имени родительского узла-актора, делая их более удобными для начинающих.
2D и 3D версии NavigationAgents доступны как NavigationAgent2D и NavigationAgent3D соответственно.
Новые узлы NavigationAgent автоматически присоединятся к навигационной карте по умолчанию на World2D/World3D.
Узлы NavigationsAgent необязательны и не являются обязательным условием для использования навигационной системы. Весь их функционал можно заменить скриптами и прямыми вызовами API NavigationServer.
Совет
Для более продвинутого использования рассмотрите Использовать NavigationPathQueryObjects вместо узлов NavigationAgent.
NavigationAgent Поиск пути
NavigationAgents запрашивает новый навигационный путь на своей текущей навигационной карте, когда для их target_position задана глобальная позиция.
На результат поиска пути можно повлиять с помощью следующих свойств.
Битовую маску
navigation_layersможно использовать для ограничения навигационных сеток, которые может использовать агент.pathfinding_algorithmуправляет тем, как поиск пути проходит по полигонам навигационной сетки при поиске пути.path_postprocessingустанавливает, будет ли и как изменяться необработанный коридор пути, найденный при поиске пути, перед его возвратом.path_metadata_flagsвключает сбор дополнительных метаданных точек пути, возвращаемых путем.Свойства
simplify_pathиsimplify_epsilonможно использовать для удаления менее важных точек из пути.
Предупреждение
Отключение метафлагов пути отключит соответствующие сигналы от агента.
NavigationAgent Pathfollowing
После того, как для агента задана target_position, следующую позицию для следования по пути можно получить с помощью функции get_next_path_position().
После получения следующей позиции пути переместите родительский узел-актор агента к этой позиции пути, используя собственный код движения.
Примечание
Навигационная система никогда не перемещает родительский узел NavigationAgent. Перемещение полностью контролируется пользователями и их скриптами.
У NavigationAgents есть собственная внутренняя логика для продолжения текущего пути и запроса обновлений.
Функция get_next_path_position() отвечает за обновление многих внутренних состояний и свойств агента. Функцию следует вызывать один раз каждый physics_process, пока is_navigation_finished() не сообщит о завершении пути. Функцию не следует вызывать после достижения целевой позиции или конца пути, так как это может привести к дрожанию агента на месте из-за повторяющихся обновлений пути. Всегда проверяйте в самом начале скрипта с помощью is_navigation_finished(), завершен ли путь.
На поведение следования по пути влияют следующие свойства расстояния.
На расстоянии
path_desired_distanceот следующей позиции пути агент перемещает свой внутренний индекс пути на следующую позицию пути.На расстоянии
target_desired_distanceот целевой позиции пути агент считает, что целевая позиция достигнута, а путь — завершен.На расстоянии
path_max_distanceот идеального пути до следующей позиции пути агент запрашивает новый путь, поскольку он был смещен слишком далеко.
Все важные обновления запускаются с помощью функции get_next_path_position() при ее вызове в _physics_process().
NavigationAgents можно использовать с process, но они по-прежнему ограничены одним обновлением, которое происходит в physics_process.
Примеры скриптов для различных узлов, обычно используемых с NavigationAgents, можно найти ниже.
Распространенные проблемы следования по пути
При написании сценариев перемещения агентов следует учитывать некоторые распространенные проблемы пользователей и важные предостережения.
- Путь возвращается пустым
Если агент запрашивает путь до синхронизации навигационной карты, например, в функции
_ready(), путь может быть пустым. В этом случае функцияget_next_path_position()вернёт ту же позицию, что и родительский узел агента, и агент будет считать, что путь достигнут. Это исправляется отложенным вызовом или использованием обратного вызова, например, ожиданием сигнала об изменении навигационной карты.
- Агент застрял, танцуя между двумя позициями
Обычно это происходит из-за слишком частого обновления пути в каждом кадре, как намеренного, так и случайного (например, слишком короткое максимальное расстояние пути). Поиск пути должен находить ближайшие допустимые позиции на навигационной сетке. Если новый путь запрашивается каждый кадр, первые позиции пути могут постоянно меняться перед и за текущей позицией агента, что приводит к его метанию между двумя позициями.
- Агент иногда отступает
Если агент движется очень быстро, он может превысить проверку path_desired_distance, не увеличивая индекс пути. Это может привести к тому, что агент будет возвращаться к точке пути, находящейся за ним, пока не пройдёт проверку расстояния, чтобы увеличить индекс пути. Увеличение желаемых расстояний соответственно скорости и частоте обновления агента обычно решает эту проблему, как и более сбалансированная структура полигонов навигационной сетки, в которой не так много рёбер полигонов сжаты в ограниченном пространстве.
- Агент иногда смотрит назад в поисках кадра
Как и в случае с застрявшими танцующими агентами между двумя позициями, это обычно вызвано очень частым обновлением траектории в каждом кадре. В зависимости от макета навигационной сетки, и особенно когда агент размещается непосредственно над ребром навигационной сетки или его соединением, следует ожидать, что траектория движения иногда будет немного отставать от текущей ориентации актеров. Это происходит из-за проблем с точностью и не всегда может быть предотвращено. Обычно эта проблема заметна только в том случае, если актеры мгновенно поворачиваются лицом к текущей траектории.
NavigationAgent избежание
В этом разделе объясняется, как использовать функцию избегания навигации, характерную для NavigationAgents.
Чтобы NavigationAgents могли использовать функцию избегания, свойству avoidance_enabled должно быть присвоено значение true.
Для получения результата расчета безопасной скорости необходимо подключить сигнал velocity_computed узла NavigationAgent.
Установите velocity узла NavigationAgent в _physics_process(), чтобы обновить агента с использованием текущей скорости родительского узла агента.
Если на агенте включена функция уклонения, вектор safe_velocity будет приниматься вместе с сигналом velocity_computed в каждом физическом кадре. Этот вектор скорости следует использовать для перемещения родительского узла NavigationAgent, чтобы избежать столкновений с другими агентами, использующими функцию уклонения, или препятствиями.
Примечание
При расчете избегания будут учитываться только те агенты на той же карте, которые зарегистрированы для избегания.
Следующие свойства NavigationAgent имеют значение для избегания:
Свойство
высотадоступно только в 3D. Высота вместе с текущим глобальным положением агента по оси Y определяет его вертикальное положение при моделировании уклонения. Агенты, использующие 2D-уклонение, автоматически игнорируют других агентов и препятствия, находящиеся ниже или выше них.Свойство
radiusуправляет размером круга уклонения, или, в случае трёхмерной сферы, вокруг агента. Эта область описывает тело агента, а не расстояние, на которое выполняется манёвр уклонения.Свойство
neighbor_distanceуправляет радиусом поиска агента при поиске других агентов, которых следует избегать. Более низкое значение снижает стоимость обработки.Свойство
max_neighborsопределяет количество других агентов, учитываемых при расчёте избегания, если все они имеют перекрывающийся радиус. Более низкое значение снижает стоимость обработки, но слишком низкое может привести к тому, что агенты будут игнорировать избегание.Свойства
time_horizon_agentsиtime_horizon_obstaclesуправляют временем прогнозирования уклонения от других агентов или препятствий в секундах. При расчете безопасных скоростей агенты выбирают скорости, которые они могут поддерживать в течение указанного количества секунд, не сталкиваясь с другими объектами уклонения. Время прогнозирования следует поддерживать как можно короче, поскольку за это время агенты будут снижать скорость, чтобы избежать столкновения.Свойство
max_speedуправляет максимальной скоростью, допустимой для расчёта скорости уклонения агентов. Если родительские агенты движутся быстрее этого значения, скорость уклоненияsafe_velocityможет быть недостаточно точной для предотвращения столкновений.Свойство
use_3d_avoidanceпереключает агента между режимами двумерного избегания (ось xz) и трёхмерного избегания (ось xyz) при следующем обновлении. Обратите внимание, что двумерное и трёхмерное избегание выполняются в отдельных симуляциях, поэтому агенты, разделённые между ними, не влияют друг на друга.Свойства
avoidance_layersиavoidance_maskпредставляют собой битовые маски, аналогичные, например, физическим слоям. Агенты будут избегать только тех объектов избегания, которые находятся на слое избегания, совпадающем хотя бы с одним из битов их собственной маски избегания.Параметр
avoidance_priorityзаставляет агентов с более высоким приоритетом игнорировать агентов с более низким приоритетом. Это можно использовать для повышения важности некоторых агентов в симуляции избегания, например, важных неигровых персонажей, без постоянного изменения всех их уровней избегания или масок.
Механизм уклонения существует в своём собственном пространстве и не использует информацию из навигационных сеток или физических столкновений. По сути, агенты уклонения — это просто круги с разным радиусом на плоской двумерной плоскости или сферы в пустом трёхмерном пространстве. NavigationObstacles можно использовать для добавления ограничений среды к моделированию механизма уклонения, см. Использовать NavigationObstacles.
Примечание
Избегание не влияет на поиск пути. Его следует рассматривать как дополнительную возможность для постоянно движущихся объектов, которые невозможно эффективно (пере)запечь в навигационной сетке для перемещения вокруг них.
Примечание
Избегание RVO предполагает неявные предположения о естественном поведении агентов. Например, что агенты движутся по разумным сторонам разъезда, которые можно назначить при их столкновении. Это означает, что даже самые клинические сценарии избегания обычно терпят неудачу. Например, агенты, движущиеся прямо друг на друга с абсолютно противоположными скоростями, не сработают, поскольку агентам невозможно назначить стороны разъезда.
Использование свойства avoidance_enabled объекта NavigationAgent является предпочтительным вариантом для включения/выключения режима избегания. Следующие фрагменты кода можно использовать для включения/выключения режима избегания для агентов, создания или удаления обратных вызовов для избегания, а также для переключения режимов избегания.
extends NavigationAgent2D
func _ready() -> void:
var agent: RID = get_rid()
# Enable avoidance
NavigationServer2D.agent_set_avoidance_enabled(agent, true)
# Create avoidance callback
NavigationServer2D.agent_set_avoidance_callback(agent, Callable(self, "_avoidance_done"))
# Disable avoidance
NavigationServer2D.agent_set_avoidance_enabled(agent, false)
# Delete avoidance callback
NavigationServer2D.agent_set_avoidance_callback(agent, Callable())
using Godot;
public partial class MyNavigationAgent2D : NavigationAgent2D
{
public override void _Ready()
{
Rid agent = GetRid();
// Enable avoidance
NavigationServer2D.AgentSetAvoidanceEnabled(agent, true);
// Create avoidance callback
NavigationServer2D.AgentSetAvoidanceCallback(agent, Callable.From(AvoidanceDone));
// Disable avoidance
NavigationServer2D.AgentSetAvoidanceEnabled(agent, false);
//Delete avoidance callback
NavigationServer2D.AgentSetAvoidanceCallback(agent, default);
}
private void AvoidanceDone() { }
}
extends NavigationAgent3D
func _ready() -> void:
var agent: RID = get_rid()
# Enable avoidance
NavigationServer3D.agent_set_avoidance_enabled(agent, true)
# Create avoidance callback
NavigationServer3D.agent_set_avoidance_callback(agent, Callable(self, "_avoidance_done"))
# Switch to 3D avoidance
NavigationServer3D.agent_set_use_3d_avoidance(agent, true)
# Disable avoidance
NavigationServer3D.agent_set_avoidance_enabled(agent, false)
# Delete avoidance callback
NavigationServer3D.agent_set_avoidance_callback(agent, Callable())
# Switch to 2D avoidance
NavigationServer3D.agent_set_use_3d_avoidance(agent, false)
using Godot;
public partial class MyNavigationAgent3D : NavigationAgent3D
{
public override void _Ready()
{
Rid agent = GetRid();
// Enable avoidance
NavigationServer3D.AgentSetAvoidanceEnabled(agent, true);
// Create avoidance callback
NavigationServer3D.AgentSetAvoidanceCallback(agent, Callable.From(AvoidanceDone));
// Switch to 3D avoidance
NavigationServer3D.AgentSetUse3DAvoidance(agent, true);
// Disable avoidance
NavigationServer3D.AgentSetAvoidanceEnabled(agent, false);
//Delete avoidance callback
NavigationServer3D.AgentSetAvoidanceCallback(agent, default);
// Switch to 2D avoidance
NavigationServer3D.AgentSetUse3DAvoidance(agent, false);
}
private void AvoidanceDone() { }
}
Шаблоны скриптов NavigationAgent
В следующих разделах приведены шаблоны скриптов для узлов, обычно используемых с NavigationAgents.
extends Node2D
@export var movement_speed: float = 4.0
@onready var navigation_agent: NavigationAgent2D = get_node("NavigationAgent2D")
var movement_delta: float
func _ready() -> void:
navigation_agent.velocity_computed.connect(Callable(_on_velocity_computed))
func set_movement_target(movement_target: Vector2):
navigation_agent.set_target_position(movement_target)
func _physics_process(delta):
# Do not query when the map has never synchronized and is empty.
if NavigationServer2D.map_get_iteration_id(navigation_agent.get_navigation_map()) == 0:
return
if navigation_agent.is_navigation_finished():
return
movement_delta = movement_speed * delta
var next_path_position: Vector2 = navigation_agent.get_next_path_position()
var new_velocity: Vector2 = global_position.direction_to(next_path_position) * movement_delta
if navigation_agent.avoidance_enabled:
navigation_agent.set_velocity(new_velocity)
else:
_on_velocity_computed(new_velocity)
func _on_velocity_computed(safe_velocity: Vector2) -> void:
global_position = global_position.move_toward(global_position + safe_velocity, movement_delta)
extends CharacterBody2D
@export var movement_speed: float = 4.0
@onready var navigation_agent: NavigationAgent2D = get_node("NavigationAgent2D")
func _ready() -> void:
navigation_agent.velocity_computed.connect(Callable(_on_velocity_computed))
func set_movement_target(movement_target: Vector2):
navigation_agent.set_target_position(movement_target)
func _physics_process(delta):
# Do not query when the map has never synchronized and is empty.
if NavigationServer2D.map_get_iteration_id(navigation_agent.get_navigation_map()) == 0:
return
if navigation_agent.is_navigation_finished():
return
var next_path_position: Vector2 = navigation_agent.get_next_path_position()
var new_velocity: Vector2 = global_position.direction_to(next_path_position) * movement_speed
if navigation_agent.avoidance_enabled:
navigation_agent.set_velocity(new_velocity)
else:
_on_velocity_computed(new_velocity)
func _on_velocity_computed(safe_velocity: Vector2):
velocity = safe_velocity
move_and_slide()
extends RigidBody2D
@export var movement_speed: float = 4.0
@onready var navigation_agent: NavigationAgent2D = get_node("NavigationAgent2D")
func _ready() -> void:
navigation_agent.velocity_computed.connect(Callable(_on_velocity_computed))
func set_movement_target(movement_target: Vector2):
navigation_agent.set_target_position(movement_target)
func _physics_process(delta):
# Do not query when the map has never synchronized and is empty.
if NavigationServer2D.map_get_iteration_id(navigation_agent.get_navigation_map()) == 0:
return
if navigation_agent.is_navigation_finished():
return
var next_path_position: Vector2 = navigation_agent.get_next_path_position()
var new_velocity: Vector2 = global_position.direction_to(next_path_position) * movement_speed
if navigation_agent.avoidance_enabled:
navigation_agent.set_velocity(new_velocity)
else:
_on_velocity_computed(new_velocity)
func _on_velocity_computed(safe_velocity: Vector2):
linear_velocity = safe_velocity
using Godot;
public partial class MyNode2D : Node2D
{
[Export]
public float MovementSpeed { get; set; } = 4.0f;
NavigationAgent2D _navigationAgent;
private float _movementDelta;
public override void _Ready()
{
_navigationAgent = GetNode<NavigationAgent2D>("NavigationAgent2D");
_navigationAgent.VelocityComputed += OnVelocityComputed;
}
private void SetMovementTarget(Vector2 movementTarget)
{
_navigationAgent.TargetPosition = movementTarget;
}
public override void _PhysicsProcess(double delta)
{
// Do not query when the map has never synchronized and is empty.
if (NavigationServer2D.MapGetIterationId(_navigationAgent.GetNavigationMap()) == 0)
{
return;
}
if (_navigationAgent.IsNavigationFinished())
{
return;
}
_movementDelta = MovementSpeed * (float)delta;
Vector2 nextPathPosition = _navigationAgent.GetNextPathPosition();
Vector2 newVelocity = GlobalPosition.DirectionTo(nextPathPosition) * _movementDelta;
if (_navigationAgent.AvoidanceEnabled)
{
_navigationAgent.Velocity = newVelocity;
}
else
{
OnVelocityComputed(newVelocity);
}
}
private void OnVelocityComputed(Vector2 safeVelocity)
{
GlobalPosition = GlobalPosition.MoveToward(GlobalPosition + safeVelocity, _movementDelta);
}
}
using Godot;
public partial class MyCharacterBody2D : CharacterBody2D
{
[Export]
public float MovementSpeed { get; set; } = 4.0f;
NavigationAgent2D _navigationAgent;
public override void _Ready()
{
_navigationAgent = GetNode<NavigationAgent2D>("NavigationAgent2D");
_navigationAgent.VelocityComputed += OnVelocityComputed;
}
private void SetMovementTarget(Vector2 movementTarget)
{
_navigationAgent.TargetPosition = movementTarget;
}
public override void _PhysicsProcess(double delta)
{
// Do not query when the map has never synchronized and is empty.
if (NavigationServer2D.MapGetIterationId(_navigationAgent.GetNavigationMap()) == 0)
{
return;
}
if (_navigationAgent.IsNavigationFinished())
{
return;
}
Vector2 nextPathPosition = _navigationAgent.GetNextPathPosition();
Vector2 newVelocity = GlobalPosition.DirectionTo(nextPathPosition) * MovementSpeed;
if (_navigationAgent.AvoidanceEnabled)
{
_navigationAgent.Velocity = newVelocity;
}
else
{
OnVelocityComputed(newVelocity);
}
}
private void OnVelocityComputed(Vector2 safeVelocity)
{
Velocity = safeVelocity;
MoveAndSlide();
}
}
using Godot;
public partial class MyRigidBody2D : RigidBody2D
{
[Export]
public float MovementSpeed { get; set; } = 4.0f;
NavigationAgent2D _navigationAgent;
public override void _Ready()
{
_navigationAgent = GetNode<NavigationAgent2D>("NavigationAgent2D");
_navigationAgent.VelocityComputed += OnVelocityComputed;
}
private void SetMovementTarget(Vector2 movementTarget)
{
_navigationAgent.TargetPosition = movementTarget;
}
public override void _PhysicsProcess(double delta)
{
// Do not query when the map has never synchronized and is empty.
if (NavigationServer2D.MapGetIterationId(_navigationAgent.GetNavigationMap()) == 0)
{
return;
}
if (_navigationAgent.IsNavigationFinished())
{
return;
}
Vector2 nextPathPosition = _navigationAgent.GetNextPathPosition();
Vector2 newVelocity = GlobalPosition.DirectionTo(nextPathPosition) * MovementSpeed;
if (_navigationAgent.AvoidanceEnabled)
{
_navigationAgent.Velocity = newVelocity;
}
else
{
OnVelocityComputed(newVelocity);
}
}
private void OnVelocityComputed(Vector2 safeVelocity)
{
LinearVelocity = safeVelocity;
}
}
extends Node3D
@export var movement_speed: float = 4.0
@onready var navigation_agent: NavigationAgent3D = get_node("NavigationAgent3D")
var physics_delta: float
func _ready() -> void:
navigation_agent.velocity_computed.connect(Callable(_on_velocity_computed))
func set_movement_target(movement_target: Vector3):
navigation_agent.set_target_position(movement_target)
func _physics_process(delta):
# Save the delta for use in _on_velocity_computed.
physics_delta = delta
# Do not query when the map has never synchronized and is empty.
if NavigationServer3D.map_get_iteration_id(navigation_agent.get_navigation_map()) == 0:
return
if navigation_agent.is_navigation_finished():
return
var next_path_position: Vector3 = navigation_agent.get_next_path_position()
var new_velocity: Vector3 = global_position.direction_to(next_path_position) * movement_speed
if navigation_agent.avoidance_enabled:
navigation_agent.set_velocity(new_velocity)
else:
_on_velocity_computed(new_velocity)
func _on_velocity_computed(safe_velocity: Vector3) -> void:
global_position = global_position.move_toward(global_position + safe_velocity, physics_delta * movement_speed)
extends CharacterBody3D
@export var movement_speed: float = 4.0
@onready var navigation_agent: NavigationAgent3D = get_node("NavigationAgent3D")
func _ready() -> void:
navigation_agent.velocity_computed.connect(Callable(_on_velocity_computed))
func set_movement_target(movement_target: Vector3):
navigation_agent.set_target_position(movement_target)
func _physics_process(delta):
# Do not query when the map has never synchronized and is empty.
if NavigationServer3D.map_get_iteration_id(navigation_agent.get_navigation_map()) == 0:
return
if navigation_agent.is_navigation_finished():
return
var next_path_position: Vector3 = navigation_agent.get_next_path_position()
var new_velocity: Vector3 = global_position.direction_to(next_path_position) * movement_speed
if navigation_agent.avoidance_enabled:
navigation_agent.set_velocity(new_velocity)
else:
_on_velocity_computed(new_velocity)
func _on_velocity_computed(safe_velocity: Vector3):
velocity = safe_velocity
move_and_slide()
extends RigidBody3D
@export var movement_speed: float = 4.0
@onready var navigation_agent: NavigationAgent3D = get_node("NavigationAgent3D")
func _ready() -> void:
navigation_agent.velocity_computed.connect(Callable(_on_velocity_computed))
func set_movement_target(movement_target: Vector3):
navigation_agent.set_target_position(movement_target)
func _physics_process(delta):
# Do not query when the map has never synchronized and is empty.
if NavigationServer3D.map_get_iteration_id(navigation_agent.get_navigation_map()) == 0:
return
if navigation_agent.is_navigation_finished():
return
var next_path_position: Vector3 = navigation_agent.get_next_path_position()
var new_velocity: Vector3 = global_position.direction_to(next_path_position) * movement_speed
if navigation_agent.avoidance_enabled:
navigation_agent.set_velocity(new_velocity)
else:
_on_velocity_computed(new_velocity)
func _on_velocity_computed(safe_velocity: Vector3):
linear_velocity = safe_velocity
using Godot;
public partial class MyNode3D : Node3D
{
[Export]
public float MovementSpeed { get; set; } = 4.0f;
NavigationAgent3D _navigationAgent;
private float _movementDelta;
public override void _Ready()
{
_navigationAgent = GetNode<NavigationAgent3D>("NavigationAgent3D");
_navigationAgent.VelocityComputed += OnVelocityComputed;
}
private void SetMovementTarget(Vector3 movementTarget)
{
_navigationAgent.TargetPosition = movementTarget;
}
public override void _PhysicsProcess(double delta)
{
// Do not query when the map has never synchronized and is empty.
if (NavigationServer3D.MapGetIterationId(_navigationAgent.GetNavigationMap()) == 0)
{
return;
}
if (_navigationAgent.IsNavigationFinished())
{
return;
}
_movementDelta = MovementSpeed * (float)delta;
Vector3 nextPathPosition = _navigationAgent.GetNextPathPosition();
Vector3 newVelocity = GlobalPosition.DirectionTo(nextPathPosition) * _movementDelta;
if (_navigationAgent.AvoidanceEnabled)
{
_navigationAgent.Velocity = newVelocity;
}
else
{
OnVelocityComputed(newVelocity);
}
}
private void OnVelocityComputed(Vector3 safeVelocity)
{
GlobalPosition = GlobalPosition.MoveToward(GlobalPosition + safeVelocity, _movementDelta);
}
}
using Godot;
public partial class MyCharacterBody3D : CharacterBody3D
{
[Export]
public float MovementSpeed { get; set; } = 4.0f;
NavigationAgent3D _navigationAgent;
public override void _Ready()
{
_navigationAgent = GetNode<NavigationAgent3D>("NavigationAgent3D");
_navigationAgent.VelocityComputed += OnVelocityComputed;
}
private void SetMovementTarget(Vector3 movementTarget)
{
_navigationAgent.TargetPosition = movementTarget;
}
public override void _PhysicsProcess(double delta)
{
// Do not query when the map has never synchronized and is empty.
if (NavigationServer3D.MapGetIterationId(_navigationAgent.GetNavigationMap()) == 0)
{
return;
}
if (_navigationAgent.IsNavigationFinished())
{
return;
}
Vector3 nextPathPosition = _navigationAgent.GetNextPathPosition();
Vector3 newVelocity = GlobalPosition.DirectionTo(nextPathPosition) * MovementSpeed;
if (_navigationAgent.AvoidanceEnabled)
{
_navigationAgent.Velocity = newVelocity;
}
else
{
OnVelocityComputed(newVelocity);
}
}
private void OnVelocityComputed(Vector3 safeVelocity)
{
Velocity = safeVelocity;
MoveAndSlide();
}
}
using Godot;
public partial class MyRigidBody3D : RigidBody3D
{
[Export]
public float MovementSpeed { get; set; } = 4.0f;
NavigationAgent3D _navigationAgent;
public override void _Ready()
{
_navigationAgent = GetNode<NavigationAgent3D>("NavigationAgent3D");
_navigationAgent.VelocityComputed += OnVelocityComputed;
}
private void SetMovementTarget(Vector3 movementTarget)
{
_navigationAgent.TargetPosition = movementTarget;
}
public override void _PhysicsProcess(double delta)
{
// Do not query when the map has never synchronized and is empty.
if (NavigationServer3D.MapGetIterationId(_navigationAgent.GetNavigationMap()) == 0)
{
return;
}
if (_navigationAgent.IsNavigationFinished())
{
return;
}
Vector3 nextPathPosition = _navigationAgent.GetNextPathPosition();
Vector3 newVelocity = GlobalPosition.DirectionTo(nextPathPosition) * MovementSpeed;
if (_navigationAgent.AvoidanceEnabled)
{
_navigationAgent.Velocity = newVelocity;
}
else
{
OnVelocityComputed(newVelocity);
}
}
private void OnVelocityComputed(Vector3 safeVelocity)
{
LinearVelocity = safeVelocity;
}
}