This document is about: QUANTUM 2
SWITCH TO

Custom Navmesh Generation

舉例而言,自訂Quantum導航網格的生成或嵌入的理由是:

  • 為了生成更高的準確性,以使代理能夠在3D導航網格上正確的行走。
  • 為了在運行階段動態地生成一個導航網格(警告:SDK仍然有部分的工具鏈不是確定性,請聯絡我們)。

這個章節將提供一個概述,說明如何延展導航網格工具。

生成嵌入資料

建議的流程是生成中繼導航網格格式MapNavMesh.BakeData及透過MapNavMeshBaker.BakeNavMesh(MapData data, MapNavMesh.BakeData navmeshBakeData)運行這個,其使用來自BakeData的三角形資訊以填入Quantum導航網格的所需的資料架構。

MapNavMeshBaker.BakeNavMesh()被開發為用於編輯的時候,並且完全地替換它可能產生效能改善,但也可能是一個更加耗費心力的工作。

MapNavMesh.BakeData層級

類型欄位說明
字串名稱導航網格的名稱,透過f.Map.NavMeshes[name]在模擬中可存取
向量3位置導航網格的位置。最終導航網格頂點儲存在全域空間,並且它們的位置在嵌入時由此來翻譯。
FP代理半徑建立導航網格的最大的代理的半徑。較舊的Quantum版本曾允許不同的代理半徑,但是這已經被廢除。現在代理可以一直向前走,直到它們的樞紐位於導航網格的邊緣上。這樣代理應該遠離牆壁的邊界地方將被嵌入三角形之中。此值只用於轉譯偵錯圖形。
清單<字串>區域 用於這個導航網格的所有區域識別碼。在嵌入區域時,識別碼會被新增到地圖資產的區域清單,並且它們的索引被嵌入到導航網格三角形區域遮罩(NavMeshTriangle.Regions)。區域被彙總在地圖上,因為一個地圖可以有多個導航網格,並且它們可以共享區域識別碼。
地圖導航網格頂點[]頂點導航網格的頂點。
地圖導航網格三角形[]三角形導航網格的三角形。這是一個常規的網格資料架構,其中三角形及頂點被保存在兩個分離的陣列中,並且三角形指向頂點陣列以標記它們的3個頂點。
地圖導航網格連結[]Links在同樣的導航網格上的位置之間的連結。
列舉最近三角形計算Quantum導航網格使用一個格線來進行空間分割。各個格線單元格將被指派一個遞補三角形。預設搜尋相當緩慢(BruteForce),雖然SpiralOut更有效率,但是它可能導致空的遞補三角形。
整數最近三角形計算深度格線單元格數量以延展SpiralOut搜尋。
bool啟用Quantum_XY當啟用後,導航網格嵌入將翻轉頂點位置的Y及Z元件,以支援在XY平面生成的導航網格。
布林值連結錯誤校正在嵌入時自動地校正導航網格位置到最近的三角形。

地圖導航網格三角形層級

三角形應具有順時鐘的捲繞順序。
不是所有欄位都應該被填入。有些欄位只用於傳統導航網格繪製工具。

類型欄位說明
字串Id不需要
字串[]頂點識別碼至少有3個單位的長度。被參照的頂點作為識別碼。SDK 2.1.或更早版本所需要。
整數32[]頂點識別碼2至少有3個單位的長度。被參照的頂點作為索引被放入陣列中。SDK 2.2.版本所需要。
整數32區域不需要
字串區域識別碼這個三角形屬於的區域。預設是空值
FP成本三角形的成本。預設應為FP._1

地圖導航網格頂點層級

Position的類型已經由FPVector3 SDK 2.2.所取代。

類型欄位說明
字串識別碼SDK 2.1或更早版本所需要
向量3位置頂點的位置
清單<整數32>芳鄰不需要
清單<整數32>三角形不需要

地圖導航網格連結層級

StartEndCostOveride的類型在SDK 2.2中已經各自由FPVector3FP所取代。

類型欄位說明
向量3開始連結的開始位置。必須在同樣的導航網格上。
向量3結束連結的結束位置。必須在同樣的導航網格上。
布林值雙向連結可從兩個方向周遊。
浮點數成本覆寫連線的成本。
字串區域識別碼連結屬於的區域識別碼。預設是空值
字串名稱連結的名稱。可透過navmesh.Links[NavMeshPathfinder.CurrentLink()].Name來查詢。

程式碼片段

C#

// Generate simple navmesh BakeData
var bakeData = new MapNavMesh.BakeData() {
  AgentRadius = FP._0_20,
  ClosestTriangleCalculation = MapNavMesh.FindClosestTriangleCalculation.SpiralOut,
  ClosestTriangleCalculationDepth = 1,
  Name = "DynamicNavmesh",
  PositionFP = FPVector3.Zero,
  Regions = new System.Collections.Generic.List<string>(),
  Vertices = new MapNavMeshVertexFP[] {
    new MapNavMeshVertexFP { Position = FPVector3.Forward },
    new MapNavMeshVertexFP { Position = FPVector3.Right },
    new MapNavMeshVertexFP { Position = -FPVector3.Forward},
    new MapNavMeshVertexFP { Position = -FPVector3.Right},
  },
  Triangles = new MapNavMeshTriangle[] {
    new MapNavMeshTriangle { VertexIds2 = new int[] { 0, 1, 2}, Cost = FP._1 },
    new MapNavMeshTriangle { VertexIds2 = new int[] { 0, 2, 3}, Cost = FP._1 }
  }
};

在啟動模擬之前替換導航網格資產

在啟動模擬之前替換一個現存的Unity Quantum導航網格資產的內容。所有的客戶端及延遲加入者需要執行這件事。

要求:

  • 針對SDK 2.1的navmesh-deterministic-baking分支(請聯絡我們)
  • BakeData生成是確定性的

替換資產必須在透過UnityDB載入導航網格之前及啟動模擬之前完成。在這個程式碼片段中,載入Unity導航網格資產以取代在它之中的Quantum資產(.Settings),並且複製GuidPath值。使.DataAsset失效將防止二進位導航網格資產(_data資產)的還原序列化,這是在當其最終由Quantum載入的時候。

C#

var navmesh = MapNavMeshBaker.BakeNavMesh(mapdata.Asset.Settings, bakeData, null);
var navmeshAsset = UnityEngine.Resources.Load<NavMeshAsset>("PathToNavmeshAsset");
navmesh.Guid = navmeshAsset.Settings.Guid;
navmesh.Path = navmeshAsset.Settings.Path;
navmeshAsset.Settings = navmesh;
navmeshAsset.Settings.DataAsset.Id = AssetGuid.Invalid;

// QuantumRunner.StartGame()

在運行階段插入導航網格

地圖資產的限制

Map資產帶有兩個查閱,在載入地圖及相關導航網格時,用於方便地進行被填入的導航網格及區域名稱查閱。兩個查閱都不能用於動態導航網格資產。

C#

public Dictionary<String, NavMesh> NavMeshes;
public Dictionary<String, Int32> RegionMap;
public NavMesh GetNavMesh(String name) {}

GetNavMesh()替代方案

Map (f.Map.GetNavMesh(name))中的導航網格查閱不能用於動態導航網格,因為在Map上的字典的修改不能用於延遲加入者或重新連線玩家。取而代之地,使用這個程式碼片段,以搜尋所有導航網格:

C#

public static NavMesh FindNavmeshByName(Frame f, string name) {
  var result = f.Map.GetNavMesh(name);
  if (result != null) {
    return result;
  }

  foreach (var a in f.DynamicAssetDB.Assets) {
    if (a is NavMesh navmeshAsset) {
      if (navmeshAsset.Name == name) {
        return navmeshAsset;
      }
    }
  }

  return null;
}

替代性地,建立及管理一個儲存在f.Globals上的導航網格查閱:

C#

global {
  dictionary<QStringUtf8<32>, asset_ref<NavMesh>> Navmeshes;
}

在動態導航網格上使用區域

如果一個動態導航網格有自己的區域,它必須重新使用所有由靜態地圖及其他動態區域載入的區域。如果它有新的區域,它們不被允許使用已經使用的相同的區域識別碼。切換將無法正常使用。

最好離線建立一個靜態RegionMap,並且在動態生成導航網格中使用它。在Map.Loaded()時從地圖Region成員來建立RegionMap

C#

public string[] Regions;

在模擬中插入導航網格

確定性地建立NavmeshBakeData,並且在運行階段在模擬中嵌入一個Quantum導航網格。使用Quantum動態資料庫。

要求:

  • 針對SDK 2.1,navmesh-deterministic-baking分支(請聯絡我們)
  • NavmeshBakeData的建立需要是確定性的
  • 必須在一個verified幀時被執行

導航網格嵌入程式碼已經被移動到(在2.1中已經被複製到)quantum.code專案,以能夠在Unity外運行。

C#

using Quantum.Experimental;

// May be more buffer required for serialization
private static byte[] _byteStreamData = new byte[1024 * 1024];

// Generate bake data
var bakeData = new NavmeshBakeData() {
  AgentRadius = FP._0_20,
  ClosestTriangleCalculation = NavMeshBakeDataFindClosestTriangle.SpiralOut,
  ClosestTriangleCalculationDepth = 1,
  Name = "DynamicNavmesh",
  PositionFP = FPVector3.Zero,
  Regions = new List<string>(),
  Vertices = new Experimental.NavmeshBakeDataVertex[] {
    new NavmeshBakeDataVertex { Position = FPVector3.Forward },
    new NavmeshBakeDataVertex  { Position = FPVector3.Right },
    new NavmeshBakeDataVertex  { Position = -FPVector3.Forward},
    new NavmeshBakeDataVertex  { Position = -FPVector3.Right},
  },
  Triangles = new NavmeshBakeDataTriangle[] {
    new NavmeshBakeDataTriangle { VertexIds = new int[] { 0, 1, 2}, Cost = FP._1 },
    new NavmeshBakeDataTriangle { VertexIds = new int[] { 0, 2, 3}, Cost = FP._1 }
  }
};

// Bake navmesh asset
var navmesh = NavmeshBaker.BakeNavMesh(f.Map, bakeData);

// Create and add binary navmesh data asset (to support late joiners)
var byteStream = new ByteStream(_byteStreamData);
navmesh.Serialize(byteStream, true);
var binaryDataAsset = new BinaryData();
binaryDataAsset.Data = byteStream.ToArray();
var binaryDataAssetRef = new AssetRefBinaryData();
binaryDataAssetRef.Id = f.AddAsset(binaryDataAsset);
navmesh.DataAsset = binaryDataAssetRef;

// Add navmesh to Dynamic DB
f.AddAsset(navmesh);

同時使用來自下一個章節的FindNavmeshByName()程式碼片段,以正確地透過名稱找到動態導航網格資產。

從Unity插入導航網格

用於延遲加入者。必須由 一個 客戶端初始化。

要求:

在Unity中在 一個 客戶端上建立BakeData,並且使用資產插入命令:

C#

var map = QuantumRunner.Default.Game.Frames.Verified.Map;
// Bake navmesh
var navmesh = MapNavMeshBaker.BakeNavMesh(map, bakeData, null);
var data = AssetInjectionUtility.SerializeAsset(null, navmesh);
// Adjust PlayerRef 0
AssetInjectionUtility.InjectAsset(QuantumRunner.Default.Game, 0, bakeData.Name, data);

為了轉譯動態導航網格的Gizmos,在QuantumGameGizmos.cs中更改下列行:

C#

// ################## NavMeshes ##################

if (editorSettings.DrawNavMesh) {
  var listOfNavmeshes = new System.Collections.Generic.List<NavMesh>();
  if (editorSettings.DrawNavMesh) {
     listOfNavmeshes.AddRange(frame.Map.NavMeshes.Values);
  }
  if (frame.DynamicAssetDB.IsEmpty == false) {
     listOfNavmeshes.AddRange(frame.DynamicAssetDB.Assets.Where(a => a is NavMesh).Select(a => (NavMesh)a).ToList());
  }
  foreach (var navmesh in listOfNavmeshes) {
    // ...
  }
}
Back to top