Custom Navmesh Generation
在運行時更改和構建 Quantum 導航網格具有挑戰性。未來版本的 Quantum 導航將著重於動態靈活性。目前有一些更具動態性的尋路解決方案,它們與程序式運行時關卡生成更兼容,例如基於瓦片的尋路器、流場等。
對於程序生成的導航網格,有兩種可行的方法:
- 在一個客戶端上生成導航網格,然後共享給其他客戶端
- 確定性地生成
BakeData
,並在每個客戶端本地烘焙
生成和共享導航網格
一個客戶端使用 Unity 工具烘焙導航網格,然後共享給其他每個客戶端。由於烘焙過程耗時且影響較大,最好在任何客戶端啟動 Quantum 模擬之前同步完成。
- 烘焙 Unity 導航網格
MapNavMesh.ImportFromUnity()
MapNavMeshBaker.BakeNavMesh()
- 上傳到文件服務器
- 從文件服務器下載
- 替換導航網格
- 啟動Quantum
可以通過使用admin
Photon 客戶端或利用自定義後端來選擇生成導航網格的客戶端。
我們之前推薦使用 Quantum 資產注入和 DynamicAssets 作為自訂和共享導航網格的工具,這仍然是可行的,但它只在導航網格資產較小(例如小於 1MB)時效果良好,否則在啟動期間發送大量數據會給即時協議帶來很大的網絡負擔。
替換導航網格二進制數據
C#
// Make sure to dispose the UnityDB after each game
UnityDB.Dispose();
// Load the pre-existing navmesh data asset
var navmeshData = UnityDB.FindAsset<BinaryDataAsset>(4350045557267041182).Settings;
// This would need to be replaced by a downloaded file from a server
var newNavmeshData = UnityEngine.Resources.Load<BinaryDataAsset>("NewNavmeshData");
// Replace the navmesh data that is loaded with the pre-existing navmesh asset
// Grid and world sizes etc must match, regions will be added to the map correctly
navmeshData.Data = newNavmeshData.Settings.Data;
// QuantumRunner.StartGame()
確定性生成導航網格 BakeData
這種方法不需要 Unity 導航網格烘焙。相反,它通過程序生成導航網格的部分內容,然後烘焙成 Quantum 導航網格。因為輸入數據(BakeData)本身已是導航網格,而非 Unity 導航網格生成所用的碰撞體集合,所以當導航網格可以由預先生成的部分拼接而成,或者三角形可以按簡單模式生成時,這種解決方案效果很好。有關 BakeData 對象的內部結構,請參見下面的部分。
要求:
MapNavMesh.BakeData
的創建需要具有確定性
每個客戶端以確定性的方式單獨生成 BakeData,這消除了與其他客戶端共享它的必要性,但後來加入的客戶端也必須經歷這個過程。
生成的 BakeData 將用於生成 Quantum 導航網格。
C#
// Bake navmesh asset
var navmesh = NavmeshBaker.BakeNavMesh(f.Map, bakeData);
結果也可以像前面的部分那樣替換一個虛擬導航網格數據資產,或者用運行時導航網格替換現有的導航網格。
在啟動前替換運行時導航網格
以這種方式替換資產必須在通過UnityDB
加載導航網格之前以及模擬開始之前完成。加載 Unity 導航網格資產是為了替換其中的 Quantum 資產(.Settings
),並復制Guid
和Path
值。使.DataAsset
無效將防止 Quantum 最終加載二進制導航網格資產(_data
資產)時對其進行反序列化。
C#
// BakeData needs to be procedurally generated
var bakeData = default(NavmeshBakeData);
var newNavmesh = NavmeshBaker.BakeNavMesh(map, bakeData);
// Load navmesh asset to replace
var navmeshAsset = UnityEngine.Resources.Load<NavMeshAsset>("DB/TestNavMeshAgents/NavMeshToReplace");
// Replace the navmesh content
newNavmesh.Guid = navmeshAsset.Settings.Guid;
newNavmesh.Path = navmeshAsset.Settings.Path;
navmeshAsset.Settings = newNavmesh;
// Invalidate the navmesh data (because it has been generated during runtime it's already loaded)
navmeshAsset.Settings.DataAsset.Id = AssetGuid.Invalid;
navmeshAsset.Settings.Name = "MyNavmesh";
// QuantumRunner.StartGame()
在啟動期間替換運行時導航網格
在這個代碼示例中,在OnGameStart
和OnGameResync
回調期間,已加載的虛擬導航網格被運行時生成的導航網格替換。
通過這種方式也可以在遊戲期間替換導航網格:
- 只能在經過驗證的幀期間
- 所有烘焙導航網格的信息和參數必須在該幀上可用(例如單例組件或全局變量),以便後來加入的玩家在重新同步時能夠生成正確的導航網格。
C#
namespace Quantum {
using Quantum.Experimental;
public class RuntimeNavmeshBaking : QuantumCallbacks {
public override void OnGameStart(QuantumGame game) {
// In this sample the bake data has already been created, but it should be assembled during runtime
var bakeData = UnityEngine.Resources.Load<BakeDataSO>("BakeData");
ReplaceNavmesh(game.Frames.Verified.Map, 1356438205741681193, bakeData.BakeData);
}
public override void OnGameResync(QuantumGame game) {
var bakeData = UnityEngine.Resources.Load<BakeDataSO>("BakeData");
ReplaceNavmesh(game.Frames.Verified.Map, 1356438205741681193, bakeData.BakeData);
}
private static void ReplaceNavmesh(Map map, AssetGuid navmeshGuid, NavmeshBakeData bakeData) {
var newNavmesh = NavmeshBaker.BakeNavMesh(map, bakeData);
var navmeshAsset = UnityDB.FindAsset<NavMeshAsset>(navmeshGuid);
// cannot change name or regions without manipulating the map too
navmeshAsset.Settings.GridSizeX = newNavmesh.GridSizeX;
navmeshAsset.Settings.GridSizeY = newNavmesh.GridSizeY;
navmeshAsset.Settings.GridNodeSize = newNavmesh.GridNodeSize;
navmeshAsset.Settings.WorldOffset = newNavmesh.WorldOffset;
navmeshAsset.Settings.MinAgentRadius = newNavmesh.MinAgentRadius;
navmeshAsset.Settings.Triangles = newNavmesh.Triangles;
navmeshAsset.Settings.TrianglesGrid = newNavmesh.TrianglesGrid;
navmeshAsset.Settings.Vertices = newNavmesh.Vertices;
navmeshAsset.Settings.BorderGrid = newNavmesh.BorderGrid;
navmeshAsset.Settings.TrianglesCenterGrid = newNavmesh.TrianglesCenterGrid;
navmeshAsset.Settings.Borders = newNavmesh.Borders;
navmeshAsset.Settings.Links = newNavmesh.Links;
}
}
}
namespace Quantum {
using Quantum.Experimental;
using UnityEngine;
public class BakeDataSO : ScriptableObject {
public NavmeshBakeData BakeData;
}
}
MapNavMesh.BakeData
中間導航網格格式MapNavMesh.BakeData
是一個確定性的網格,構建為帶有附加數據的三角形條帶,在運行時可以通過MapNavMeshBaker.BakeNavMesh(MapData data, MapNavMesh.BakeData navmeshBakeData)
轉換為 Quantum 導航網格資產。
MapNavMesh.BakeData類
類型 | 字段 | 描述 |
---|---|---|
String | Name | 導航網格的名稱,可在模擬中通過f.Map.NavMeshes[name] 存取 |
Vector3 | Position | 導航網格的位置。最終的導航網格頂點以全局空間存儲,在烘焙期間它們的位置會由此位置進行平移。 |
FP | AgentRadius | 導航網格所針對的最大代理的半徑。舊版本的Quantum允許不同的代理半徑,但現在已取消。現在,代理可以一直走到它們的樞軸點位於導航網格邊緣的位置。這樣,代理應該遠離牆壁的邊距就被烘焙到三角形中。這個值只用於渲染調試圖形。 |
List<string> | Regions | 此導航網格中使用的所有區域ID。在烘焙期間,區域ID將添加到地圖資產的區域列表中,它們的索引被烘焙到導航網格三角形的區域掩碼(NavMeshTriangle.Regions )中。這些區域在地圖上匯總,因為一個地圖可以有多個共享區域ID的導航網格。 |
MapNavMeshVertex[] | Vertices | 導航網格的頂點。 |
MapNavMeshTriangle[] | Triangles | 導航網格的三角形。這是一種常規的網格數據結構,其中三角形和頂點分別存儲在兩個數組中,三角形指向頂點數組以標記其3個頂點。 |
MapNavMeshLink[] | Links | 同一導航網格上不同位置之間的鏈接。 |
enum | ClosestTriangleCalculation | Quantum導航網格使用網格進行空間分區。每個網格單元會分配一個備用三角形。默認的搜索相當慢(BruteForce ),而SpiralOut 更高效,但可能會導致備用三角形為空。 |
int | ClosestTriangleCalculationDepth | 搜索要擴展的網格單元數量。 |
bool | EnableQuantum_XY | 啟用時,導航網格烘焙將翻轉頂點位置的Y和Z分量,以支持在XY平面中生成的導航網格。 |
bool | LinkErrorCorrection | 在烘焙期間自動將導航網格鏈接位置校正到最近的三角形。 |
MapNavMeshTriangle類
三角形預期為順時針繞組順序。
並非所有字段都需要填寫。其中一些只用於舊式導航網格繪製工具。
類型 | 字段 | 描述 |
---|---|---|
String | Id | 非必需 |
String[] | VertexIds | 長度必須為3。以ID形式引用的頂點。SDK 2.1或更早版本需要。 |
Int32[] | VertexIds2 | 長度必須為3。以頂點數組中的索引形式引用的頂點。SDK 2.2需要。 |
Int32 | Area | 非必需 |
String | RegionId | 此三角形所屬的區域。默認為null 。 |
FP | Cost | 三角形的成本。默認值應為FP._1 。 |
MapNavMeshVertex類
在 SDK 2.2 中,Position
的類型已替換為FPVector3
.
類型 | 字段 | 描述 |
---|---|---|
String | Id | SDK 2.1或更早版本需要 |
Vector3 | Position | 頂點的位置 |
List<Int32> | Neighbors | 非必需 |
List<Int32> | Triangles | 非必需 |
MapNavMeshLink類
在 SDK 2.2 中,Start
, End
和CostOveride
的類型分別替換為FPVector3
和FP
。
類型 | 字段 | 描述 |
---|---|---|
Vector3 | Start | 鏈接的起始位置。必須位於同一導航網格上。 |
Vector3 | End | 鏈接的結束位置。必須位於同一導航網格上。 |
bool | Bidirectional | 鏈接是否可以從兩個方向穿越。 |
float | CostOverride | 連接的成本。 |
String | RegionId | 鏈所屬的區域ID。默認為null 。 |
String | Name | 鏈接的名稱。可以通過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 }
}
};
Back to top