This document is about: QUANTUM 2
SWITCH TO

カスタム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クラス

項目説明
StringNameシミュレ―ション内で、f.Map.NavMeshes[name]によってアクセスできるNavMeshの名前
Vector3PositionNavmeshの位置。最終的なnavmeshの頂点はグローバルスペースに格納され、その位置はベイク時にこれによって変換されます。
FPAgentRadiusNavmeshが作成される最大のエージェントの半径。Quantumの古いバージョンでは、異なるエージェントの半径を許可していましたが、これは廃止されました。現在では、エージェントはピボットがNavmeshの端にくるまで歩くことができます。この方法では、エージェントが壁から離れるべきマージンが三角形にベイクされます。この値はデバッググラフィックをレンダリングするためにのみ使用されます。
List<string>Regions このNavmeshで使用されている全てのリージョンID。リージョンIDはベイク中にMapアセットのリージョンリストに追加され、そのインデックスはNavmeshの三角形リージョンマスク (NavMeshTriangle.Regions) にベイクされます。マップはリージョンIDを共有する複数のNavmeshを持つことができるため、リージョンはマップ上に集約されます。
MapNavMeshVertex[]VerticesNavMeshの頂点。
MapNavMeshTriangle[]TrianglesNavmeshの三角形。これは通常のメッシュデータ構造で、三角形と頂点は2つの別々の配列に保持され、三角形はその3つの頂点をマークするために頂点配列を指します。
MapNavMeshLink[]Links同じNavmesh上の位置間のリンク。
enumClosestTriangleCalculationQuantum Navmeshは、空間分割にグリッドを使用しています。各グリッドセルにはフォールバック三角形が割り当てられます。デフォルトの探索はかなり遅い (BruteForce) ですが、SpiralOutはもっと効率的です。ただし、空のフォールバック三角形になる可能性があります。
intClosestTriangleCalculationDepthSpiralOut検索を拡張するためのグリッドセルの数
boolEnableQuantum_XY有効にすると、NavmeshベイキングはXY平面で生成されたNavmeshをサポートするために、頂点位置のYおよびZ成分を反転させます。
boolLinkErrorCorrectionベイキング中に、Navmeshリンクの位置を自動的に最も近い三角形に修正します。

MapNavMeshTriangleクラス

すべての項目に入力が必要なわけではありません。レガシーNavmesh描画ツールにのみ必要な項目もあります。

項目説明
StringId不要
String[]VertexIds長さが3である必要があります。 参照される頂点をIDで表します。SDK 2.1.以前のバージョンで必要です。
Int32[]VertexIds2長さが3である必要があります。参照される頂点は頂点配列のインデックスです。SDK 2.2 で必要です。
Int32Area不要
StringRegionIdこの三角形があるリージョン。デフォルトはnullです。
FPCost三角形のコスト。デフォルトはFP._1です。

MapNavMeshVertexクラス

Positionの型は、SDK 2.2ではFPVector3に置き換わりました。

項目説明
StringIdSDK 2.1以前のバージョンで必要です。
Vector3Position頂点の位置
List<Int32>Neighbors不要
List<Int32>Triangles不要

SDK 2.2ではStartEndCostOverideはそれぞれFPVector3FPに置き換わりました。

項目説明
Vector3Startリンクの開始位置。同じNavmesh上にする必要があります。
Vector3Endリンクの終了位置。同じNavmesh上にする必要があります。
boolBidirectionalリンクは両方向から横断できます。
floatCostOverride接続のコスト。
StringRegionIdリンクがあるリージョンのID。デフォルトはnullです。
StringNameリンクの名前。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 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()

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にリージョンがある場合には、静的マップと他の動的リージョンによって読み込まれたすべてのリージョンを再利用する必要があります。もし新しいリージョンがある場合には、すでに使用されているものと同じリージョンIDを使用することはできません。トグル機能が正常に機能しなくなるためです。

1つの静的なRegionMapをオフラインで作成し、それを動的に生成されたNavmesh内で使用するのが最適です。RegionMapは、Map.Loaded()の際にマップのRegionメンバーから作成されます。

C#

public string[] Regions;

ランタイム時に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