カスタムNavmeshの生成
Quantum Navmeshの生成やベイクをカスタマイズする理由としては、例えば以下のようなものがあります:
- エージェントが3D Navmesh上を適切に歩けるよう、より精度の高いNavmeshを生成するため。
- ランタイム中に動的にNavmeshを生成するため(警告:SDKにはまだ決定論的でないツールチェインの部分があります。お問い合わせください。)
このセクションでは、Navmeshツールを拡張する方法の概要を説明します。
BakeDataの生成
推奨されるプロセスでは中間的なNavmeshフォーマットである MapNavMesh.BakeData
を生成し、これを MapNavMeshBaker.BakeNavMesh(MapData data, MapNavMesh.BakeData navmeshBakeData)
で実行します。これは BakeData
から得られる三角形の情報を使ってQuantum Navmeshの必要なデータ構造をすべて埋めます。
MapNavMeshBaker.BakeNavMesh()` は編集時に使用するために開発され、完全に置き換えるとパフォーマンスが向上しますが、より複雑なタスクになります。
MapNavMesh.BakeDataクラス
型 | 項目 | 説明 |
---|---|---|
String | Name | シミュレ―ション内で、f.Map.NavMeshes[name] によってアクセスできるNavMeshの名前 |
Vector3 | Position | Navmeshの位置。最終的なnavmeshの頂点はグローバルスペースに格納され、その位置はベイク時にこれによって変換されます。 |
FP | AgentRadius | Navmeshが作成される最大のエージェントの半径。Quantumの古いバージョンでは、異なるエージェントの半径を許可していましたが、これは廃止されました。現在では、エージェントはピボットがNavmeshの端にくるまで歩くことができます。この方法では、エージェントが壁から離れるべきマージンが三角形にベイクされます。この値はデバッググラフィックをレンダリングするためにのみ使用されます。 |
List<string> | Regions | このNavmeshで使用されている全てのリージョンID。リージョンIDはベイク中にMapアセットのリージョンリストに追加され、そのインデックスはNavmeshの三角形リージョンマスク (NavMeshTriangle.Regions) にベイクされます。マップはリージョンIDを共有する複数のNavmeshを持つことができるため、リージョンはマップ上に集約されます。 |
MapNavMeshVertex[] | Vertices | NavMeshの頂点。 |
MapNavMeshTriangle[] | Triangles | Navmeshの三角形。これは通常のメッシュデータ構造で、三角形と頂点は2つの別々の配列に保持され、三角形はその3つの頂点をマークするために頂点配列を指します。 |
MapNavMeshLink[] | Links | 同じNavmesh上の位置間のリンク。 |
enum | ClosestTriangleCalculation | Quantum Navmeshは、空間分割にグリッドを使用しています。各グリッドセルにはフォールバック三角形が割り当てられます。デフォルトの探索はかなり遅い (BruteForce ) ですが、SpiralOut はもっと効率的です。ただし、空のフォールバック三角形になる可能性があります。 |
int | ClosestTriangleCalculationDepth | SpiralOut 検索を拡張するためのグリッドセルの数 |
bool | EnableQuantum_XY | 有効にすると、NavmeshベイキングはXY平面で生成されたNavmeshをサポートするために、頂点位置のYおよびZ成分を反転させます。 |
bool | LinkErrorCorrection | ベイキング中に、Navmeshリンクの位置を自動的に最も近い三角形に修正します。 |
MapNavMeshTriangleクラス
すべての項目に入力が必要なわけではありません。レガシーNavmesh描画ツールにのみ必要な項目もあります。
型 | 項目 | 説明 |
---|---|---|
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クラス
Position
の型は、SDK 2.2では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 | リンクの開始位置。同じNavmesh上にする必要があります。 |
Vector3 | End | リンクの終了位置。同じNavmesh上にする必要があります。 |
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 }
}
};
シミュレーションの開始前にNavmeshアセットを置換
シミュレーションを開始する前に、既存のUnity Quantum Navmeshアセットの内容を置換します。全てのクライアントと、遅れての参加者がこれを実行する必要があります。
要件:
- SDK 2.1向けの
navmesh-deterministic-baking
ブランチ (弊社までご連絡ください) BakeData
生成は予測にもとづきます。
アセットの置換は、UnityDB
によってNavmeshが読み込まれる前、かつシミュレーションが開始する前におこなう必要があります。このスニペットでは、(.Settings
)内のQuantumアセットを置換するために、Unity NavMeshアセットが読み込まれ、また
Guid
およびPath
の値がコピーされます。.DataAsset
を無効にすると、バイナリNavmeshアセット(_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()
ランタイム時にNavmeshを再設定
Mapアセットによる制限
Map
アセットには、Navmeshおよびリージョン名ルックアップという、2つのルックアップがあり、地図と関連するNavmeshがロードされるとその情報が入力されます。これらのルックアップは、動的なNavmeshアセットでは動作しません。
C#
public Dictionary<String, NavMesh> NavMeshes;
public Dictionary<String, Int32> RegionMap;
public NavMesh GetNavMesh(String name) {}
GetNavMesh()の代替
Map
(f.Map.GetNavMesh(name)
)のNavMeshは、動的Navmeshには使用できません。これは、遅れての参加者や再接続したプレイヤーについては、Map
上のディクショナリの修正をおこなえないためです。その代わりに、すべてのNavmeshを検索するために以下のスニペットを使用してください:
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
に保存されたNavmeshルックアップを作成および管理してください:
C#
global {
dictionary<QStringUtf8<32>, asset_ref<NavMesh>> Navmeshes;
}
動的Navmesh上でリージョンを使用
動的Navmeshにリージョンがある場合には、静的マップと他の動的リージョンによって読み込まれたすべてのリージョンを再利用する必要があります。もし新しいリージョンがある場合には、すでに使用されているものと同じリージョンIDを使用することはできません。トグル機能が正常に機能しなくなるためです。
1つの静的なRegionMap
をオフラインで作成し、それを動的に生成されたNavmesh内で使用するのが最適です。RegionMap
は、Map.Loaded()
の際にマップのRegion
メンバーから作成されます。
C#
public string[] Regions;
シミュレーション内にNavmeshを再設定
ランタイム時にNavmeshBakeData
を予測で作成し、シミュレーション内にQuantum NavMeshをベイクします。これは、Quantumの動的データベースを使用します。
要件:
- SDK 2.1向けの
navmesh-deterministic-baking
ブランチ (弊社までご連絡ください) NavmeshBakeData
の作成は予測にもとづく必要があります。確認した
フレームで実行する必要があります。
NavMeshベイキングコードはquantum.code
プロジェクトに移動され(2.1内にコピー)、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);
名前で動的NavMeshアセットを正しく検索するには、次のセクションのFindNavmeshByName()
も使用してください。
UnityからNavmeshを再設定
遅れての参加者用です。1つのクライアントで初期化する必要があります。
要件:
1つのクライアント上でUnity内にBakeData
を作成し、AssetInjectionコマンドを使用します:
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);
動的Navmeshの変更をレンダリングするには、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