使用 NavigationAgent
NavigationAgent 即导航代理,是一种辅助节点,能够为继承自 Node2D/3D 的父节点提供寻路、路径跟随、代理躲避等功能。这类节点会代替父级角色节点对 NavigationServer API 进行常见的调用,针对初学者进行了优化。
2D 和 3D 版本的 NavigationAgent 分别是 NavigationAgent2D 和 NavigationAgent3D。
新建的 NavigationAgent 节点会自动加入 World2D/World3D 的默认导航地图。
NavigationsAgent 节点是可选的,不是使用导航系统的硬性要求。对应的功能都可以用脚本代替,替换为对 NavigationServer API 的直接调用。
NavigationAgent 寻路
当 target_position
设置为全局位置时,NavigationAgent会在其当前导航地图上查询新的导航路径。
匀速遍历, 然后, 可以用下面的伪代码。
navigation_layers
位元遮罩可用于限制代理可以使用的导航网格。pathfinding_algorithm
控制路径搜索中路径查找如何通过导航网格多边形。path_postprocessing
设置在路径查找找到的原始路径走廊返回之前是否或如何更改。path_metadata_flags
允许收集路径返回的附加路径点元资料。The
simplify_path
andsimplify_epsilon
properties can be used to remove less critical points from the path.
警告
禁用路径元旗标将禁用代理上的相关信号发射。
NavigationAgent 路径跟随
为代理程序设定 target_position
后,可以使用 get_next_path_position()
函式来撷取路径中要遵循的下一个位置。
收到下一个路径位置,使用你自己的移动代码将代理的父参与者节点移到此路径位置。
备注
导览系统永远不会移动NavigationAgent的父节点。该动作完全掌握在使用者及其自定义脚本的手中。
NavigationAgent有自己的内部逻辑来处理当前路径并调用更新。
get_next_path_position()
函数负责更新代理的许多内部状态和属性。你需要在每个 physics_process
中都调用该函数,且仅调用一次,直到 is_navigation_finished()
的返回值显示该路径已完成;到达目标位置或路径末端后,就不应再调用该函数了,否则将反复更新路径,可能导致代理在原地抖动。一定要尽早在脚本中使用 is_navigation_finished()
检查路径是否已完成。
The following distance properties influence the path following behavior.
At
path_desired_distance
from the next path position, the agent advances its internal path index to the subsequent next path position.At
target_desired_distance
from the target path position, the agent considers the target position to be reached and the path at its end.At
path_max_distance
from the ideal path to the next path position, the agent requests a new path because it was pushed too far off.
当在 _physics_process()
中调用 get_next_path_position()
函数时,所有重要的更新都会被触发。
NavigationAgent可以与 process
一起使用,但仍限于在 physics_process
中发生的单个更新。
以下是NavigationAgent常用的各种节点的脚本示例。
运行以下命令
在编写代理移动脚本时,需要考虑一些常见的用户问题和重要的注意事项。
- 路径返回为空
如果代理在导航地图同步之前查询路径,例如在
_ready()
函数中,路径可能返回空。在这种情况下,get_next_path_position()
函数将返回与代理父节点相同的位置,并且代理将考虑到达的路径末端。这是通过进行延迟调用或使用回调来解决的,例如等待导航地图更改信号。
- 代理在两个位置跳跃
这通常是由于每帧非常频繁的路径更新造成的,无论是有意还是无意(例如最大路径距离设定得太短)。寻路需要找到导览网格上有效的最近位置。如果每帧都请求新路径,则第一个路径位置可能最终会在代理目前位置的前后不断切换,导致其在两个位置之间跳动。
- 代理有时会回溯
如果代理程序移动得非常快,它可能会超出path_desired_distance检查,而不会推进路径索引。这可能导致代理回溯到它后面的路径点,直到它通过距离检查以增加路径索引。根据代理速度和更新速率相应地增加所需距离通常可以解决此问题,并且可以使用更平衡的导览网格多边形布局,而不会在小空间内挤在一起太多的多边形边缘。
- 代理有时会向后寻找影格
与代理卡在两个位置之间不断跳跃的情况相同,这通常是由每帧过于频繁的路径更新引起的。根据导航网格的不同布局,特别是当代理是被直接放置在导航网格的边缘或边缘连接上时,路径位置有时会稍微“落后”于演员的朝向。这是精度问题导致的,有时难以避免。此现象通常只有在演员被瞬间旋转并面向当前路径位置时,才会造成肉眼可见的问题。
NavigationAgent 避障
本节解释了如何使用 NavigationAgent 的导航避障功能。
要让 NavigationAgent 使用避障功能,必须将 enable_avoidance
属性设置为 true
。

必须连接 NavigationAgent 节点的 velocity_computed
信号,接收安全速度的计算结果。

请在 _physics_process()
中设置 NavigationAgent 节点的 velocity
,利用代理父节点的当前速度来更新代理。
只要代理开启了避障,就可以在每个物理帧通过 velocity_computed 信号收到 safe_velocity
向量。应该使用这个速度向量来移动 NavigationAgent 的父节点,这样就能够避免撞到其他使用了避障的代理以及避障障碍物。
备注
计算避障时只会考虑位于同一张地图中其他注册了避障的代理。
NavigationAgent 中与避障相关的属性如下:
属性
height
仅在 3D 中可用。高度与代理的当前全局 y 轴位置一起决定了代理在回避模拟中的垂直位置。使用 2D 回避的代理将自动忽略其下方或上方的其他代理或障碍物。属性
radius
控制迴避圆的大小,或者在3D球体的情况下,控制代理周围的大小。该区域描述的是代理的身体,而不是躲避机动距离。当搜索应避免的其他代理时,属性
neighbor_distance
控制代理的搜索半径。较低的值可降低处理成本。属性
max_neighbors
控制在避免计算中考虑多少其他代理(如果它们都具有重叠半径)。较低的值降低了处理成本,但过低的值可能导致代理忽略避免。属性
time_horizon_agents
和time_horizon_obstacles
控制其他代理或障碍物的回避预测时间(以秒为单位)。当特工计算他们的安全速度时,他们选择的速度可以保持这一秒,而不会与另一个躲避物体碰撞。预测时间应尽可能低,因为代理会减慢速度以避免在该时间段内发生碰撞。属性
max_speed
控制代理回避计算所允许的最大速度。如果代理父母移动得比这个值快,则避免safe_velocity
可能不够准确,无法避免碰撞。属性
use_3d_avoidance
在下一次更新时在2D回避(xz轴)和3d回避(xyz轴)之间切换代理。请注意,2D回避和3D回避在单独的回避模拟中运行,因此在它们之间划分的代理不会相互影响。属性
avoidance_layers
和avoidance_mask
是类似于例如物理层的位掩码。代理将仅避开位于与其自己的回避掩码位中的至少一个相匹配的回避层上的其他回避对象。The
avoidance_priority
makes agents with a higher priority ignore agents with a lower priority. This can be used to give certain agents more importance in the avoidance simulation, e.g. important non-playable characters, without constantly changing their entire avoidance layers or mask.
回避存在于其自身的空间中,并且没有来自导航网格或物理碰撞的信息。场景背后的回避代理只是平面2D平面上具有不同半径的圆或其他空的3D空间中的球体。导航障碍物可用于将一些环境约束添加到规避模拟中,请参见 使用 NavigationObstacle 。
备注
回避不会影响寻路。它应该被视为一个额外的选项,用于不断移动无法有效地(重新)烘焙到导航网格中以在其周围移动的对象。
备注
RVO(Recursive Velocity Obstacles)避障算法对智能体的自然行为做出了隐含假设。例如,假设智能体相遇时会移动到合理的通行侧。这意味着非常严格的避障测试场景通常会失败。例如,两个以完全相反速度直接相对移动的智能体会失败,因为这些智能体无法确定它们的通行侧。
使用NavigationAgent enable_avoidance
属性是切换回避的首选选项。以下代码段可用于在代理上切换回避、创建或删除回避回调或切换回避模式。
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 movement_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):
# 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
movement_delta = movement_speed * delta
var next_path_position: Vector3 = navigation_agent.get_next_path_position()
var new_velocity: Vector3 = 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: Vector3) -> void:
global_position = global_position.move_toward(global_position + safe_velocity, movement_delta)
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;
}
}