使用 NavigationObstacle

2D 和 3D 版本的 NavigationObstacle 节点分别为 NavigationObstacle2DNavigationObstacle3D

障碍物具有双重作用,可以影响导航网格烘焙和代理避障。

  • 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.

这可以用来避免在不需要的地方烘焙出导航网格,例如,类似厚墙的实心集合体的内部,或屋顶等不在游戏范围内的区域。

导航障碍物弃置不需要的导航网格

导航障碍物弃置不需要的导航网格。

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

更多关于导航网格烘焙的信息见 使用导航网格

../../_images/nav_mesh_obstacles_properties.webp

属性 affect_navigation_mesh 使得障碍物能够参与到导航网格的烘焙过程中。它将像导航网格烘焙过程中的其他节点对象一样被解析或未解析。

属性 carve_navigation_mesh 使得形状不受烘焙过程中的偏移影响,例如由导航网格的 agent_radius 添加的偏移。它基本上会像一个模板一样,在已经加上偏移的导航网格表面上切割。它仍然会受到烘焙过程后期处理的影响,比如边缘简化。

障碍物的形状和放置位置由 heightvertices 属性以及障碍物的 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)

障碍物与代理避障

避障导航中的障碍物可以是静态障碍物也可以是动态障碍物,能够影响启用了避障处理的代理。

  • 当静态使用时,导览障碍会限制多边形定义区域外部或内部的回避控制代理。

  • 当动态使用时,导览障碍会推开其周围半径范围内的回避控制代理。

静态避障障碍物

避障障碍物的 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)