2D 導覽總覽
Godot 提供多種物件、類別與伺服器,協助 2D 與 3D 遊戲實作基於格狀或網格的導覽與尋路。以下將快速總覽 Godot 中所有 2D 場景可用的導覽相關物件及其主要用途。
Godot 提供以下物件與類別以支援 2D 導覽:
- Astar2D
Astar2D物件可在具有權重的**點**構成的圖中尋找最短路徑。AStar2D 類別最適合用於格狀(儲存格)為基礎的 2D 遊戲,不需角色到達區域中任意位置,而僅需到達預先定義的特定位置。
- AstarGrid2D
AstarGrid2D是 AStar2D 的變體,專為部分 2D 格狀結構(Grid)設計。AstarGrid2D 在適用時更容易使用,因為不用手動建立點並將它們連接起來。
- NavigationServer2D
NavigationServer2D提供強大的伺服器 API,可在由導覽網格定義的區域中尋找兩個位置間的最短路徑。NavigationServer 最適合用於需讓角色移動至導覽網格區域內任意位置的 2D 即時遊戲。網格(Mesh)導覽適用於大型遊戲世界,因為一大片區域常可僅用單一多邊形定義,若用格子則需許多儲存格。
NavigationServer 會管理多個導覽地圖,每張地圖由數個包含導覽網格資料的區域組成。代理可被放置於地圖上進行避障計算。與伺服器溝通時,會以 RID 來引用內部地圖、區域與代理。
- 可用的 NavigationServer RID 型別如下。
- 導覽地圖 RID
引用特定的導覽地圖,地圖中包含區域與代理。地圖會根據區域間距離自動合併導覽網格,並於每個物理影格同步區域與代理。
- 導覽區域 RID
引用特定導覽區域,該區域可存放導覽網格資料。可啟用或停用區域,並可用導覽層(navigation layer)位元遮罩限制其用途。
- 導覽連結 RID
引用特定導覽連結,可連接導覽網格上兩個任意距離的位置。
- 導覽代理 RID
引用特定避障代理。避障效果可透過半徑值設定。
- 導覽障礙 RID
引用特定避障障礙,可用來影響及限制代理的避障速度。
以下場景樹節點可作為輔助元件,協助操作 NavigationServer2D API。
- NavigationRegion2D 節點
此節點持有 NavigationPolygon 資源,定義供 NavigationServer2D 使用的導覽網格。
區域可設為啟用或停用。
可透過
navigation_layers位元遮罩進一步限制其在尋路時的用途。NavigationServer2D 會根據區塊間的距離,自動將不同區塊的導覽網格合併為一個整體導覽網格。
- NavigationLink2D 節點
此節點可連接導覽網格上兩個任意距離的位置,供尋路使用。
連結可設為啟用或停用。
連結可設為單向或雙向。
可透過
navigation_layers位元遮罩進一步限制其在尋路時的用途。
連結會告知尋路系統有此連接,並指定其消耗。實際的代理控制與移動需於自訂腳本處理。
- NavigationAgent2D 節點
此輔助節點可簡化常用的 NavigationServer2D 尋路與避障 API 呼叫。請將本節點作為 Node2D 派生節點的子節點使用。
- NavigationObstacle2D 節點
此節點可用來影響及限制啟用避障功能的代理的避障速度。注意:此節點**不會**影響代理的尋路,若要改變尋路必須修改導覽網格。
2D 導覽網格由下列資源定義:
- NavigationPolygon 資源
存放 2D 導覽網格資料的資源。提供多邊形繪製工具,可於編輯器內或執行時定義導覽區域。
NavigationRegion2D 節點會使用該資源來定義其導覽區域。
NavigationServer2D 會用此資源更新各區塊的導覽網格。
TileSet 編輯器在定義圖塊導覽區域時,會內部建立並使用此資源。
也參考
你可以透過 2D Navigation Polygon 與 使用 AStarGrid2D 的格狀導覽 等範例專案,實際了解 2D 導覽的運作方式。
2D 場景設定
以下步驟說明如何建立最基本可用的 2D 導覽設定,會用到 NavigationServer2D 與 NavigationAgent2D 來進行路徑移動。
在場景中新增一個 NavigationRegion2D 節點。
點選該區塊節點,並新增一個 NavigationPolygon 資源至該節點。
使用 NavigationPolygon 繪圖工具定義可移動的導覽區域,然後在工具列點選「Bake NavigationPolygon」按鈕。
備註
導覽網格定義角色(中心點)可以站立與移動的區域。請於導覽多邊形邊緣與碰撞物件間預留足夠距離,避免路徑跟隨角色被碰撞體卡住。
在場景新增 CharacterBody2D 節點,設置基本碰撞形狀與 Sprite 或 Mesh 作為顯示用。
在角色節點下新增一個 NavigationAgent2D 子節點。
將下列腳本加入 CharacterBody2D 節點。我們會確保於場景完全載入、NavigationServer 完成同步後再設定移動目標。
extends CharacterBody2D
var movement_speed: float = 200.0
var movement_target_position: Vector2 = Vector2(60.0,180.0)
@onready var navigation_agent: NavigationAgent2D = $NavigationAgent2D
func _ready():
# These values need to be adjusted for the actor's speed
# and the navigation layout.
navigation_agent.path_desired_distance = 4.0
navigation_agent.target_desired_distance = 4.0
# Make sure to not await during _ready.
actor_setup.call_deferred()
func actor_setup():
# Wait for the first physics frame so the NavigationServer can sync.
await get_tree().physics_frame
# Now that the navigation map is no longer empty, set the movement target.
set_movement_target(movement_target_position)
func set_movement_target(movement_target: Vector2):
navigation_agent.target_position = movement_target
func _physics_process(delta):
if navigation_agent.is_navigation_finished():
return
var current_agent_position: Vector2 = global_position
var next_path_position: Vector2 = navigation_agent.get_next_path_position()
velocity = current_agent_position.direction_to(next_path_position) * movement_speed
move_and_slide()
using Godot;
public partial class MyCharacterBody2D : CharacterBody2D
{
private NavigationAgent2D _navigationAgent;
private float _movementSpeed = 200.0f;
private Vector2 _movementTargetPosition = new Vector2(70.0f, 226.0f);
public Vector2 MovementTarget
{
get { return _navigationAgent.TargetPosition; }
set { _navigationAgent.TargetPosition = value; }
}
public override void _Ready()
{
base._Ready();
_navigationAgent = GetNode<NavigationAgent2D>("NavigationAgent2D");
// These values need to be adjusted for the actor's speed
// and the navigation layout.
_navigationAgent.PathDesiredDistance = 4.0f;
_navigationAgent.TargetDesiredDistance = 4.0f;
// Make sure to not await during _Ready.
Callable.From(ActorSetup).CallDeferred();
}
public override void _PhysicsProcess(double delta)
{
base._PhysicsProcess(delta);
if (_navigationAgent.IsNavigationFinished())
{
return;
}
Vector2 currentAgentPosition = GlobalTransform.Origin;
Vector2 nextPathPosition = _navigationAgent.GetNextPathPosition();
Velocity = currentAgentPosition.DirectionTo(nextPathPosition) * _movementSpeed;
MoveAndSlide();
}
private async void ActorSetup()
{
// Wait for the first physics frame so the NavigationServer can sync.
await ToSignal(GetTree(), SceneTree.SignalName.PhysicsFrame);
// Now that the navigation map is no longer empty, set the movement target.
MovementTarget = _movementTargetPosition;
}
}
備註
在第一幀時,NavigationServer 的地圖尚未同步區域資料,此時任何路徑查詢都會回傳空結果。請於腳本中等待一幀,讓 NavigationServer 完成同步。