Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
使用 NavigationObstacle
2D 與 3D 版本的 NavigationObstacle 節點分別為 NavigationObstacle2D 與 NavigationObstacle3D。
導覽障礙物具有雙重用途,既能影響導覽網格(Navigation Mesh)的烘焙,也能影響代理(Agent)的避障行為。
啟用
affect_navigation_mesh屬性時,障礙物會在烘焙導覽網格時發揮作用。啟用
avoidance_enabled時,障礙物會影響啟用避障的代理。
小訣竅
預設情況下會啟用避障。如果障礙物不需要用於避障,請關閉 enabled_avoidance 以節省效能。
障礙物與導覽網格
導覽障礙物影響導覽網格烘焙。
在導覽網格烘焙時,障礙物可用來排除其形狀範圍內的所有來源幾何體。
這可用來避免在不需要的區域生成導覽網格,例如在厚牆等實體幾何體內部,或是在遊戲進行時不應被包含的區域(如屋頂)上方。
導覽障礙物可用來移除不需要的導覽網格。
障礙物在烘焙過程中不會新增幾何體,只會移除幾何體。它會將障礙物形狀範圍內的所有(體素)格子設為無效,因此其效果與形狀細節會受限於烘焙時所用的格子解析度。
有關導覽網格烘焙的詳細資訊,請參考 使用導航網格。
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);
障礙物與代理避障
在避障功能中,導覽障礙物可以作為靜態或動態障礙物,影響啟用避障控制的代理。
當作為靜態障礙物時,NavigationObstacle 會限制啟用避障的代理位於多邊形所定義區域的外部或內部。
作為動態障礙物時,NavigationObstacle 會在其半徑範圍內推開啟用避障的代理。
靜態避障障礙物
若障礙物的 vertices 屬性填入了一組輪廓頂點以形成多邊形,則該避障障礙物被視為靜態。
於編輯器繪製的靜態障礙物可用來阻擋或限制導覽代理的行動。
靜態障礙物對啟用避障的代理而言,會作為不可通過的硬性邊界,類似於物理碰撞,但僅用於避障。
靜態障礙物使用一組輪廓
vertices(頂點/位置)來定義其邊界,若為 3D 則還會有一個height(高度)屬性。靜態障礙物僅適用於使用 2D 避障模式的代理。
靜態障礙物會依頂點的繞行順序決定代理是被推出還是被吸入障礙物內部。
靜態障礙物無法直接改變位置,只能將其瞬移(warp)到新位置並重新建立,因此不適合每幀都要移動位置的用途,否則會有很高的效能開銷。
被瞬移到其他位置的靜態障礙物無法被代理預測。若靜態障礙物瞬移到代理上方,就有可能讓代理被卡住。
當在 3D 場景下使用 2D 避障時,Vector3 頂點的 y 軸會被忽略,會以障礙物的全域 y 軸位置為高度。代理會自動忽略位於其上下方(超出高度範圍)的靜態障礙物,這會根據障礙物與代理的全域 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);