使用 NavigationObstacle
2D 和 3D 版本的 NavigationObstacle 节点分别为 NavigationObstacle2D 和 NavigationObstacle3D 。
障碍物具有双重作用,可以影响导航网格烘焙和代理避障。
affect_navigation_mesh
启用时,障碍物会影响导航网格的烘焙。avoidance_enabled
启用时,障碍物会影响代理避障。
小技巧
避障默认是启用的。如果障碍物不需要参与避障,请禁用 enabled_avoidance
节省性能。
障碍物与导航网格

导航障碍物对导航网格烘焙的影响。
For navigation mesh baking, obstacles can be used to discard parts of all other source geometry inside the obstacle shape.
这可以用来避免在不需要的地方烘焙出导航网格,例如,类似厚墙的实心集合体的内部,或屋顶等不在游戏范围内的区域。

导航障碍物弃置不需要的导航网格。
障碍物在烘焙过程中并不会添加几何形状,它只会移除几何形状。它是通过消除障碍物形状内部所有被栅格化源几何覆盖的(体素)单元格来实现这一点的。因此,它的效果和形状细节受限于烘焙过程中使用的单元格分辨率。
更多关于导航网格烘焙的信息见 使用导航网格。

属性 affect_navigation_mesh
使得障碍物能够参与到导航网格的烘焙过程中。它将像导航网格烘焙过程中的其他节点对象一样被解析或未解析。
属性 carve_navigation_mesh
使得形状不受烘焙过程中的偏移影响,例如由导航网格的 agent_radius
添加的偏移。它基本上会像一个模板一样,在已经加上偏移的导航网格表面上切割。它仍然会受到烘焙过程后期处理的影响,比如边缘简化。
障碍物的形状和放置位置由 height
和 vertices
属性以及障碍物的 global_position
定义。用于定义顶点的任何 Vector3 的 y 轴值会被忽略,因为障碍物是在一个平坦的水平面上投影的。
在脚本中烘焙导航网格时,可以过程式地添加障碍物作为投影遮挡。障碍物不会参与源几何的解析,所以在烘焙前立即添加它们就足够了。
var obstacle_outline = PackedVector2Array([
Vector2(-50, -50),
Vector2(50, -50),
Vector2(50, 50),
Vector2(-50, 50)
])
var navigation_mesh = NavigationPolygon.new()
var source_geometry = NavigationMeshSourceGeometryData2D.new()
NavigationServer2D.parse_source_geometry_data(navigation_mesh, source_geometry, $MyTestRootNode)
var obstacle_carve: bool = true
source_geometry.add_projected_obstruction(obstacle_outline, obstacle_carve)
NavigationServer2D.bake_from_source_geometry_data(navigation_mesh, source_geometry)
Vector2[] obstacleOutline
[
new Vector2(-50, -50),
new Vector2(50, -50),
new Vector2(50, 50),
new Vector2(-50, 50),
];
var navigationMesh = new NavigationPolygon();
var sourceGeometry = new NavigationMeshSourceGeometryData2D();
NavigationServer2D.ParseSourceGeometryData(navigationMesh, sourceGeometry, GetNode<Node2D>("MyTestRootNode"));
bool obstacleCarve = true;
sourceGeometry.AddProjectedObstruction(obstacleOutline, obstacleCarve);
NavigationServer2D.BakeFromSourceGeometryData(navigationMesh, sourceGeometry);
var obstacle_outline = PackedVector3Array([
Vector3(-5, 0, -5),
Vector3(5, 0, -5),
Vector3(5, 0, 5),
Vector3(-5, 0, 5)
])
var navigation_mesh = NavigationMesh.new()
var source_geometry = NavigationMeshSourceGeometryData3D.new()
NavigationServer3D.parse_source_geometry_data(navigation_mesh, source_geometry, $MyTestRootNode)
var obstacle_elevation: float = $MyTestObstacleNode.global_position.y
var obstacle_height: float = 50.0
var obstacle_carve: bool = true
source_geometry.add_projected_obstruction(obstacle_outline, obstacle_elevation, obstacle_height, obstacle_carve)
NavigationServer3D.bake_from_source_geometry_data(navigation_mesh, source_geometry)
Vector3[] obstacleOutline =
[
new Vector3(-5, 0, -5),
new Vector3(5, 0, -5),
new Vector3(5, 0, 5),
new Vector3(-5, 0, 5),
];
var navigationMesh = new NavigationMesh();
var sourceGeometry = new NavigationMeshSourceGeometryData3D();
NavigationServer3D.ParseSourceGeometryData(navigationMesh, sourceGeometry, GetNode<Node3D>("MyTestRootNode"));
float obstacleElevation = GetNode<Node3D>("MyTestObstacleNode").GlobalPosition.Y;
float obstacleHeight = 50.0f;
bool obstacleCarve = true;
sourceGeometry.AddProjectedObstruction(obstacleOutline, obstacleElevation, obstacleHeight, obstacleCarve);
NavigationServer3D.BakeFromSourceGeometryData(navigationMesh, sourceGeometry);
障碍物与代理避障
避障导航中的障碍物可以是静态障碍物也可以是动态障碍物,能够影响启用了避障处理的代理。
当静态使用时,导览障碍会限制多边形定义区域外部或内部的回避控制代理。
当动态使用时,导览障碍会推开其周围半径范围内的回避控制代理。
静态避障障碍物
避障障碍物的 vertices
属性填充轮廓位置数组形成多边形后,就会被认为是静态障碍物。

编辑器中绘制的静态障碍物,障碍物可以阻挡或容纳导航代理。
静态障碍物起着硬性作用——使代理无法跨越边界而回避,例如类似于物理碰撞,但用于回避。
静态障碍物用一组轮廓
vertices
(位置)定义其边界,在3D的情况下,用额外的height
属性定义其边界。静态障碍物仅适用于使用2D回避模式的代理。
如果代理人被推出或吸入,静态障碍物透过顶点的缠绕顺序来定义。
静态障碍物不能改变位置,只能先传送到新的位置,然后重新构建。因此,静态障碍物不适合位置每帧都会改变的场合,因为不断重建会产生很高的性能成本。
代理无法预测扭曲到另一个位置的静态障碍物。如果静态障碍物扭曲到代理顶部,这就会产生代理被卡住的风险。
当在3D中使用2D回避时,Vector3顶点的y轴将被忽略。相反,障碍物的全局y轴位置用作海拔高度。代理将忽略3D中低于或高于其的静态障碍物。这是由障碍物和代理的全局y轴位置自动确定为海拔高度以及它们各自的高度属性。
动态避障障碍物
避障障碍物的 radius
属性大于零时就会被认为是动态障碍物。
对于启用了避障的代理而言,动态障碍物就是一个“请离我远点”的对象,类似于它们自己躲避其他代理的行为。
动态障碍物使用
radius
半径来定义边界,2D 中是圆形,3D 中是球形。动态障碍物每一帧都可以改变位置,不会有额外的性能开销。
动态障碍物设置速度后,其他代理就能够预测移动。
动态障碍物不适合用来将代理限制在拥挤、狭窄的空间中。
虽然障碍物可以同时激活静态和动态属性,但是出于性能的考虑不建议这么做。理想情况下,障碍物移动时应该移除静态顶点、激活半径。障碍物到达目的地后则应该逐步增大半径,将其他代理推开。在障碍物的周围创造出足够大的安全区域后,就应该把静态顶点添加回来、移除半径。这样就能够避免重建静态边界后,代理因为静态障碍物的突然出现而被卡住的情况。
和代理类似,障碍物也能够使用 avoidance_layers
位掩码。自身的避障掩码中与之存在匹配位的代理都会躲避这个障碍物。
程序式障碍物
可以不借助节点,直接在脚本中使用 NavigationServer 来新建障碍物。
使用脚本创建的障碍物至少需要有一个 map
和一个 position
。动态障碍物还需要 radius
。静态障碍物还需要 vertices
属性。
# create a new "obstacle" and place it on the default navigation map.
var new_obstacle_rid: RID = NavigationServer2D.obstacle_create()
var default_map_rid: RID = get_world_2d().get_navigation_map()
NavigationServer2D.obstacle_set_map(new_obstacle_rid, default_map_rid)
NavigationServer2D.obstacle_set_position(new_obstacle_rid, global_position)
# Use obstacle dynamic by increasing radius above zero.
NavigationServer2D.obstacle_set_radius(new_obstacle_rid, 5.0)
# Use obstacle static by adding a square that pushes agents out.
var outline = PackedVector2Array([Vector2(-100, -100), Vector2(100, -100), Vector2(100, 100), Vector2(-100, 100)])
NavigationServer2D.obstacle_set_vertices(new_obstacle_rid, outline)
# Enable the obstacle.
NavigationServer2D.obstacle_set_avoidance_enabled(new_obstacle_rid, true)
// Create a new "obstacle" and place it on the default navigation map.
Rid newObstacleRid = NavigationServer2D.ObstacleCreate();
Rid defaultMapRid = GetWorld2D().NavigationMap;
NavigationServer2D.ObstacleSetMap(newObstacleRid, defaultMapRid);
NavigationServer2D.ObstacleSetPosition(newObstacleRid, GlobalPosition);
// Use obstacle dynamic by increasing radius above zero.
NavigationServer2D.ObstacleSetRadius(newObstacleRid, 5.0f);
// Use obstacle static by adding a square that pushes agents out.
Vector2[] outline =
[
new Vector2(-100, -100),
new Vector2(100, -100),
new Vector2(100, 100),
new Vector2(-100, 100),
];
NavigationServer2D.ObstacleSetVertices(newObstacleRid, outline);
// Enable the obstacle.
NavigationServer2D.ObstacleSetAvoidanceEnabled(newObstacleRid, true);
# Create a new "obstacle" and place it on the default navigation map.
var new_obstacle_rid: RID = NavigationServer3D.obstacle_create()
var default_map_rid: RID = get_world_3d().get_navigation_map()
NavigationServer3D.obstacle_set_map(new_obstacle_rid, default_map_rid)
NavigationServer3D.obstacle_set_position(new_obstacle_rid, global_position)
# Use obstacle dynamic by increasing radius above zero.
NavigationServer3D.obstacle_set_radius(new_obstacle_rid, 0.5)
# Use obstacle static by adding a square that pushes agents out.
var outline = PackedVector3Array([Vector3(-5, 0, -5), Vector3(5, 0, -5), Vector3(5, 0, 5), Vector3(-5, 0, 5)])
NavigationServer3D.obstacle_set_vertices(new_obstacle_rid, outline)
# Set the obstacle height on the y-axis.
NavigationServer3D.obstacle_set_height(new_obstacle_rid, 1.0)
# Enable the obstacle.
NavigationServer3D.obstacle_set_avoidance_enabled(new_obstacle_rid, true)
// Create a new "obstacle" and place it on the default navigation map.
Rid newObstacleRid = NavigationServer3D.ObstacleCreate();
Rid defaultMapRid = GetWorld3D().NavigationMap;
NavigationServer3D.ObstacleSetMap(newObstacleRid, defaultMapRid);
NavigationServer3D.ObstacleSetPosition(newObstacleRid, GlobalPosition);
// Use obstacle dynamic by increasing radius above zero.
NavigationServer3D.ObstacleSetRadius(newObstacleRid, 5.0f);
// Use obstacle static by adding a square that pushes agents out.
Vector3[] outline =
[
new Vector3(-5, 0, -5),
new Vector3(5, 0, -5),
new Vector3(5, 0, 5),
new Vector3(-5, 0, 5),
];
NavigationServer3D.ObstacleSetVertices(newObstacleRid, outline);
// Set the obstacle height on the y-axis.
NavigationServer3D.ObstacleSetHeight(newObstacleRid, 1.0f);
// Enable the obstacle.
NavigationServer3D.ObstacleSetAvoidanceEnabled(newObstacleRid, true);