This document is about: QUANTUM 3
SWITCH TO

Creating Navmesh Agents

導航網格代理功能分為多個組件。

  • NavMeshPathfinder:執行多執行緒路徑尋找,存儲路點和路點進度
  • NavMeshSteeringAgent:執行代理導向計算,包括加速度、速度、旋轉速度和制動
  • NavMeshAvoidanceAgent:允許代理相互避讓
  • NavMeshAvoidanceObstacle:使實體可被 NavMeshAvoidanceAgent避讓

代理組件可與Transform2DTransform3D組件配合使用。

所有組件共用一個配置NavMeshAgentConfig資產。

它們可以組合用於不同的使用場景。

完整功能 Pathfinder, SteeringAgent和AvoidanceAgent 具備所有三個組件的實體可以尋找路徑、沿路徑移動並動態避讓其他代理。
無避讓功能 Pathfinder和SteeringAgent 不會執行任何避讓程式碼,且組件不存儲任何與避讓相關的數據。

可關閉SimulationConfig.Navigation.EnableAvoidance以節省更多 CPU 資源。
自定義移動 Pathfinder和SteeringAgent 代理可以尋找並存儲路徑,但通過在自定義系統中實現移動信號ISignalOnNavMeshMoveAgent可禁用導向代理(注意:僅當代理實際有目標時才會執行回調)。

NavMeshAgentConfig.MovementType需設為Callback

必須啟用SimulationConfig.Navigation.EnableNavigationCallbacks
帶避讓的自定義移動 Pathfinder, SteeringAgent和AvoidanceAgent 除了自定義移動的要求外,ISignalOnNavMeshMoveAgentdesiredDirection參數包含經避讓調整後的移動方向。
僅路徑尋找 Pathfinder 代理執行路徑尋找、路點存儲和進度管理。導向、避讓和移動由自定義系統處理。

需要自定義移動中的設置。

若無導向組件,路點進度需依賴代理接近路點的速度信息。必須每幀設置 NavMeshPathfinder.WaypointDetectionDistanceSqr

使用實體原型建立代理

本教程需要一個包含 Quantum 設置的 Unity 場景,包括已烘焙的 Quantum 導航網格(參見導入 Unity 導航網格

  • 在場景中創建 Quantum 原型:GameObject > Quantum > Empty Entity
  • 選擇QuantumEntityPrototype,將Transform設為2D
  • 選擇QuantumEntityPrototype並啟用NavMeshPathfinder
    • 展開NavMeshPathfinder.Initial Target並設置世界Position
    • NavMeshInitial Target中,選擇已烘焙的 Quantum 導航網格資產
    • 可選擇分配NavMeshAgentConfig資產,否則使用默認配置
  • 選擇QuantumEntityPrototype並啟用NavMeshSteeringAgent
  • 向GameObject添加 Unity 視圖:right-click on the GameObject > 3D Object > Capsule,並將膠囊體向上移動 1 單位
  • 打開 Quantum Gizmos 菜單疊加層,開啟NavMesh Pathfinder
  • 按遊玩按鈕
Navmesh Agent Prototype

在代碼中建立代理

本教程需要一個包含 Quantum 設置的 Unity 場景,包括已烘焙的 Quantum 導航網格(參見導入 Unity 導航網格)。

右鍵點擊文件夾QuantumUser > Simulation,選擇Create > Quantum > System創建新的 Quantum 系統。將腳本和類重命名為NavMeshAgentTestSystem,打開NavMeshAgentTestSystem資產,並將新系統添加到DefaultSystemsConfig.Entries中。

打開 Quantum Gizmos 菜單疊加層,開啟NavMesh Pathfinder,以便在進入運行模式時顯示代理 gizmos。為了讓 Unity 創建預製體,需要添加View組件(這不在本示例範圍內)。

Navmesh Agent Code

複製以下系統代碼,該系統在OnInit()期間創建代理並設置目標位置。

C#

namespace Quantum
{
  using Photon.Deterministic;
  using UnityEngine.Scripting;

  [Preserve]
  public unsafe class NavMeshAgentTestSystem : SystemMainThread
  {
    public override void OnInit(Frame frame)
    {
      base.OnInit(frame);

      var entity = frame.Create();

      // Add a transform 3d component or 2d component
      frame.Set(entity, new Transform3D()
      {
        Position = FPVector3.Zero,
        Rotation = FPQuaternion.Identity
      });

      // Create the pathfinder component using the factory method, optionally pass a NavMeshAgentConfig
      var pathfinder = NavMeshPathfinder.Create(frame, entity, null);

      // Find the navmesh by name and set a target before adding the component
      var navmesh = frame.Map.NavMeshes["Navmesh"];
      pathfinder.SetTarget(frame, new FPVector3(12, 0, 0), navmesh);

      // Add the pathfinder and steering components to the entity
      frame.Set(entity, pathfinder);
      frame.Set(entity, new NavMeshSteeringAgent());
    }

    public override void Update(Frame frame)
    {
    }
  }
}

NavMeshPathfinder是生成路徑、保存路點和執行路點進度的主要組件。

要使組件生成路徑並移動,必須調用SetTarget()。輸入的Target位置會被保存,同時會創建一個可能經過輕微修正的InternalTarget

設置目標後,代理會處於IsActive狀態。到達目標後會自動停用。

可通過GetWaypoint()檢查路點,通過WaypointCount獲取當前存儲的路點數量,通過WaypointIndex獲取當前路點。

Stop()可用於立即停止代理。

NavMeshPathfinder.SetConfig()可在組件創建期間和運行時執行。如果代理當前正在跟隨路徑,且新配置的路點數不同,路徑將被重置。該配置會自動更新到實體的NavMeshSteeringAgentNavMeshAvoidanceAgent組件,且Speed, Acceleration, AvoidancePriority, LayerMask的值會重置為配置值。

UpdateInterval 出於性能優化考慮,可配置代理不在每個模擬幀執行路徑尋找和避讓。

UpdateInterval設為大於1的值會減少更新次數。這會降低代理的響應速度,但可節省 CPU 資源。代理實體索引用於定義具體更新的幀,確保不是所有實體都在同一幀更新。

計算公式:
updateAgent = entity.Index % agentConfig.UpdateInterval == f.Number % agentConfig.UpdateInterval

1 = 每幀更新
2 = 每隔一幀更新
8 = 每 8 幀更新,以此類推。
PathQuality 反映路徑質量,並改變 A * 啟發式函數。Good提供最佳的質量 - 性能平衡。

Fast - 使用父節點 G 值和曼哈頓距離。
Good - 在朝向目標的入口邊緣創建樞點,重新計算 G 值和曼哈頓距離。
Best - 在朝向目標的入口邊緣創建樞點,使用朝向起點的另一個樞點重新計算 G 值,並使用歐幾里得距離。
CachedWaypointCount 配置NavMeshPathfinder上緩存的路點數量。

組件上只存儲有限數量的路點,因為增加非臨時數據會降低模擬速度。

當代理開始向最後一個路點移動時,會自動重新執行路徑尋找,計算新路徑以更新路點。

緩存中存儲的第一個路點是調用SetTarget()時代理的當前位置,用於增強路點到達檢測。
MaxRepathTimeout 如果在該時間段(秒)內未到達路點,將觸發新的路徑尋找。這是用於緩解代理卡住的安全機制。設為0可禁用。
LineOfSightFunneling 若啟用,會通過視線檢查移除路點。

當導航網格區域位於主導航網格中間時(例如可破壞物體),應啟用此選項。區域引入的額外三角形有時會導致活動區域附近的路徑略顯異常。
DynamicLineOfSight 若啟用,代理每幀都會檢查是否可以跳過路點。此選項耗費較多資源,但會移除路徑上所有不必要的路點。
DynamicLineOfSightWaypointRange 類似於DynamicLineOfSight選項,但僅當代理處於路點的特定範圍內時才觸發。

設為 0 時禁用。
AutomaticTargetCorrection 若禁用,當位置偏離導航網格時,SetTarget()可能失敗。

對於 3D 導航網格,絕對不能禁用。
AutomaticTargetCorrectionRadius 搜索有效導航網格的目標周圍範圍。

此值也用於修正起始位置,否則起始位置的容差半徑為0.25

下圖中黃色X標記的是目標。修正半徑會找到導航網格上最近的有效位置,修正內部目標,同時保持輸入目標不變。

對於 3D 導航網格,絕對不能設為0。默認值為1

若不合理地增大半徑,此功能可能會耗費大量資源。
Auto correction screenshot
EnableWaypointDetection 此功能可緩解代理難以到達路點的情況(例如由於旋轉速度慢或避讓)。

參數Axis ExtendAxis Offset定義路點到達檢測軸(黑線)。若代理進入黃色區域,則認為路點已到達。
Navmesh Agent Waypoint Reached Detection Axis
路點到達檢測軸
DefaultWaypointDetectionDistance 若代理沒有NavMeshSteeringAgent組件,此值用於執行路點到達檢測,應設為代理的`max speed * delta time`。

當每幀直接設置WaypointDetectionDistanceSqr時,不使用此值。

NavMeshSteeringAgent組件是可選的,需要NavMeshPathfinder

該組件可在運行時更改AccelerationMaxSpeed。初始值從配置中獲取。

旋轉速度和加速度可通過設為0禁用。

可從組件查詢CurrentSpeedVelocity

MovementType 代理移動可直接應用於變換,或通過執行移動回調驅動自定義移動邏輯。

None - 不應用移動
Transform - 移動應用於同一實體上的Transform2DTransform3D組件
Callback - 不應用移動,但執行ISignalOnNavMeshMoveAgent信號
VerticalPositioning 此選項僅在使用 3D 變換時有效。定義代理的 y 軸位置計算方式。

None - 不應用垂直定位。
Navmesh - 對導航網格進行光線投射。Navmesh是默認值,但存在缺點,因為導航網格幾何可能過於簡化,不適合角色行走。
Physics - 對 3D 物理幾何進行光線投射。使用Physics時,必須有由 Quantum 碰撞體組成的可行走地面。
Speed 代理的最大速度。

要在運行時更改Speed,使用NavMeshSteeringAgent.MaxSpeed
AngularSpeed 代理的角速度(弧度 / 秒)。

設為0可禁用代理旋轉。
設為 200或更大可使旋轉接近即時。
Acceleration 代理的加速度。

設為0可禁用加速度。
StoppingDistance 代理在目的地前方停止的距離,以避免超過目標並幫助穩定代理。當剩餘距離小於代理每幀的移動距離時,代理總是會停止。
AutoBraking 若啟用,代理在接近目標時會開始制動。
AutoBrakingDistance 代理開始制動時與目標的距離。
ClampAgentToNavmesh 僅當MovementTypeTransform時可選。

若啟用,此選項會將代理推出非導航網格區域,類似於物理碰撞體。例如,代理可能因避讓而偏離到非導航網格區域。
ClampAgentToNavmeshCorrection 每幀代理修正的百分比。

NavMeshAvoidanceAgent組件需要NavMeshPathfinderNavMeshSteeringAgent組件才能正確運行。在添加此組件之前,必須在實體上Set()這兩個組件。

該代理通過使用優先級和遮罩與圖層過濾,執行避讓計算以避讓其他移動代理(HRVO)。初始時由NavMeshAgentConfig設置的優先級、遮罩和圖層可在運行時在組件上更改。

有關更多信息,請閱讀避讓部分。

導航網格代理回調

所有代理回調都從主線程調用,訪問和寫入其他組件和實體時不會導致多線程問題。

必須通過開啟SimulationConfig.Navigation.EnableNavigationCallbacks來啟用導航代理回調。

Simulation Config

以下信號將提供即時反饋,可用於進一步控制代理。

C#

namespace Quantum {
  public unsafe partial class NavMeshAgentTestSystem : SystemMainThread,
                                                       ISignalOnNavMeshSearchFailed,
                                                       ISignalOnNavMeshWaypointReached,
                                                       ISignalOnNavMeshMoveAgent {
    }
}
ISignalOnNavMeshSearchFailed 當代理無法在其位置和SetTarget()中設置的目標之間創建路徑時調用此信號。例如,當目的地離導航網格太遠時。

在此回調中調用SetTarget()後,必須將resetAgent參數設為false
ISignalOnNavMeshWaypointReached 當代理到達其前往目標路徑上的某個路點時調用此信號。

WaypointFlags攜帶有關路點的額外信息。

Target - 該路點是目標
LinkStart - 該路點是離線鏈接的起點
LinkEnd - 該路點是離線鏈接的終點
RepathWhenReached - 代理到達該路點時將執行重新尋路
ISignalOnNavMeshMoveAgent 當代理有目標、NavMeshAgentConfig.MovementType設為Callback且代理有NavMeshSteeringAgent組件時,調用此信號。

desiredDirection參數是內部代理導向和避讓計算得出的代理移動向量的歸一化方向。

移動代理回調的示例實現。它也可以作為 KCC 的輸入,例如:

C#

public void OnNavMeshMoveAgent(Frame frame, EntityRef entity, FPVector2 desiredDirection) {
    var agent = frame.Unsafe.GetPointer<NavMeshSteeringAgent>(entity);

    // simple demonstration how to move the agent.
    if (frame.Has<Transform2D>(entity)) {
        var transform = frame.Unsafe.GetPointer<Transform2D>(entity);
        transform->Position.X.RawValue = transform->Position.X.RawValue + ((desiredDirection.X.RawValue * frame.DeltaTime.RawValue) >> FPLut.PRECISION);
        transform->Position.Y.RawValue = transform->Position.Y.RawValue + ((desiredDirection.Y.RawValue * frame.DeltaTime.RawValue) >> FPLut.PRECISION);
        transform->Rotation = FPVector2.RadiansSignedSkipNormalize(FPVector2.Up, desiredDirection);
    } else if (frame.Has<Transform3D>(entity)) {
        var transform = frame.Unsafe.GetPointer<Transform3D>(entity);
        transform->Position.X.RawValue = transform->Position.X.RawValue + ((desiredDirection.X.RawValue * frame.DeltaTime.RawValue) >> FPLut.PRECISION);
        transform->Position.Z.RawValue = transform->Position.Z.RawValue + ((desiredDirection.Y.RawValue * frame.DeltaTime.RawValue) >> FPLut.PRECISION);
        var desiredRotation = FPVector2.RadiansSignedSkipNormalize(FPVector2.Up, desiredDirection);
        transform->Rotation = FPQuaternion.AngleAxis(desiredRotation * FP.Rad2Deg, -FPVector3.Up);
    }
}
Back to top