This document is about: QUANTUM 3
SWITCH TO

This page has not been upgraded to Quantum 3.0 yet. The information may be out of date.

カスタムナビメッシュ生成

ナビメッシュ経路探索のサポート

このセクションでは、手続き的に生成されたゲームレベルとともにナビメッシュ経路探索をサポートする方法の概要を説明します。

Quantum には完全な決定論的ナビメッシュベイキングツールはありませんが、生成されたナビメッシュをサポートするためのいくつかの代替手段があります。

実現可能な 2 つのアプローチは次のとおりです:

  • 1 つのクライアントがナビメッシュを生成し、他のクライアントと共有する
  • 各クライアントで決定論的に BakeData を生成し、ローカルでナビメッシュをベイクする

注意点として、期待されるエージェント数が高い場合 (>100)、異なる経路探索ソリューションがより良い結果をもたらすかもしれません。たとえば、タイルベースのパスファインダーやフローフィールドなどです。

以前は、アセットインジェクションとダイナミックアセットをナビメッシュをカスタマイズして共有するためのツールとして推奨しましたが、これは敏感な Quantum リアルタイムプロトコルに過度の負担をかけることになります。

ナビメッシュの生成と共有

このソリューションでは、1 つのクライアントが Unity ツールを使用してナビメッシュをベイクし、それを他のクライアントと共有します。このプロセスは長く影響が大きいため、Quantum シミュレーションを開始する前にこれを同期させるのが最良です。

何かをロードする前に Quantum アセットを安全に Quantum DB に追加する API がないため、ナビメッシュアセットとそのバイナリデータアセットのコンテンツが単に置き換えられます。

  • Unity ナビメッシュのベイク
  • MapNavMesh.ImportFromUnity()
  • MapNavMeshBaker.BakeNavMesh()
  • ファイルサーバーにアップロード
  • ファイルサーバーからダウンロード
  • ナビメッシュを置き換え
  • Quantum を開始

ナビメッシュを生成するクライアントを選択するには、admin Photon クライアントを使用するか、カスタムバックエンドを利用します。

ナビメッシュバイナリデータを置き換えるサンプル

C#

// 各ゲーム後に UnityDB を破棄することを確認してください
UnityDB.Dispose();

// 既存のナビメッシュデータアセットをロード
var navmeshData = UnityDB.FindAsset<BinaryDataAsset>(4350045557267041182).Settings;

// サーバーからファイルをダウンロードする必要があります
var newNavmeshData = UnityEngine.Resources.Load<BinaryDataAsset>("NewNavmeshData");

// 既存のナビメッシュアセットでロードされたナビメッシュデータを置き換えます
// グリッドとワールドサイズなどは一致している必要があり、地域は正しくマップに追加されます
navmeshData.Data = newNavmeshData.Settings.Data;

// QuantumRunner.StartGame()

ナビメッシュ BakeData の決定論的生成

このアプローチでは、Unity ナビメッシュのベイキングを必要とせず、ナビメッシュの一部を手続き的に生成し、それを Quantum ナビメッシュにベイクします。入力データ(BakeData)がすでにナビメッシュであり、Unity ナビメッシュ生成で使用されるコライダーのコレクションではないため、事前生成されたナビメッシュの部分を繋ぎ合わせたり、単純なパターンで三角形を生成できる場合には、うまく機能します。BakeData オブジェクトの内部については、以下のセクションを参照してください。

要件:

  • navmesh-deterministic-baking ブランチが SDK 2.1 に必要です(お問い合わせください)
  • MapNavMesh.BakeData の作成が決定論的でなければなりません

BakeData は、各クライアントで個別に決定論的に生成されるため、他のクライアントと共有する必要がなくなりますが、遅延参加者もこのプロセスを経なければなりません。

完成した BakeData は Quantum ナビメッシュを生成するために使用されます。

C#

// ナビメッシュアセットをベイク
var navmesh = NavmeshBaker.BakeNavMesh(f.Map, bakeData);

この結果は、前のセクションのようにダミーのナビメッシュデータアセットを置き換えたり、ランタイムナビメッシュで既存のナビメッシュを置き換えることができます。

新しいナビメッシュを開始前に置き換えるサンプル

この方法でアセットを置き換えるには、ナビメッシュが UnityDB を介してロードされる前、かつシミュレーションが開始される前でなければなりません。Unity ナビメッシュアセットは、Quantum アセット内にある .Settings を置き換えるためにロードされ、Guid および Path の値がコピーされます。無効化された .DataAsset は、Quantum によって最終的にロードされるときにバイナリナビメッシュアセット(_data アセット)のデシリアライズを防止します。

C#

// BakeData は手続き的に生成される必要があります
var bakeData = default(NavmeshBakeData);
var newNavmesh = NavmeshBaker.BakeNavMesh(map, bakeData);

// 置き換えるナビメッシュアセットをロード
var navmeshAsset = UnityEngine.Resources.Load<NavMeshAsset>("DB/TestNavMeshAgents/NavMeshToReplace");

// ナビメッシュ内容を置き換え
newNavmesh.Guid = navmeshAsset.Settings.Guid;
newNavmesh.Path = navmeshAsset.Settings.Path;
navmeshAsset.Settings = newNavmesh;

// ナビメッシュデータを無効化します(ランタイム中に生成されたため、すでにロードされている)
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) {
      // このサンプルでは、ベイクデータがすでに生成されていますが、実行時に組み立てる必要があります
      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);
      // 名前や地域を変更することはできません。マップも操作する必要があります。
      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 は、Quantum ナビメッシュに変換され、さらに処理される完全なメッシュです。このメッシュはトライアングルストリップの形で構築されます。

推奨されるプロセスは、中間のナビメッシュ形式 MapNavMesh.BakeData を生成し、これを MapNavMeshBaker.BakeNavMesh(MapData data, MapNavMesh.BakeData navmeshBakeData) にかけることです。これにより、BakeData からの三角形情報が Quantum ナビメッシュのすべての必要なデータ構造を満たすために使用されます。

MapNavMeshBaker.BakeNavMesh() は、エディタ時に使用するために開発されており、これを完全に置き換えることがパフォーマンスの向上をもたらすかもしれませんが、同時にはるかに複雑な作業となります。

MapNavMesh.BakeData クラス

フィールド説明
StringNameナビメッシュの名前。シミュレーション内で f.Map.NavMeshes[name] によってアクセス可能。
Vector3Positionナビメッシュの位置。最終的なナビメッシュの頂点はグローバル空間に保存され、ベイキング中にこの位置によって移動します。
FPAgentRadiusナビメッシュが作成される最大のエージェントの半径。旧バージョンの Quantum では異なるエージェント半径が許可されていましたが、これは廃止されました。今では、エージェントはそのピボットがナビメッシュの端に位置するまで歩くことができます。このように、エージェントが壁から離れておくべきマージンが三角形にベイクされます。この値はデバッググラフィックスをレンダリングするためだけに使用されます。
List<string>Regionsこのナビメッシュで使用されるすべての地域 ID。ベイキング中、地域 ID はマップアセットの地域リストに追加され、そのインデックスがナビメッシュ三角形の地域マスクにベイクされます (NavMeshTriangle.Regions)。ナビメッシュは複数のナビメッシュが地域 ID を共有できるため、マップ上に集約されます。
MapNavMeshVertex[]Verticesナビメッシュの頂点。
MapNavMeshTriangle[]Trianglesナビメッシュの三角形。このデータ構造は、三角形と頂点が二つの別々の配列に保持され、三角形はそれぞれの三つの頂点を指定するために頂点配列にポイントします。
MapNavMeshLink[]Links同じナビメッシュ内の位置間のリンク。
enumClosestTriangleCalculationQuantum ナビメッシュは空間パーティショニングのためにグリッドを使用しています。各グリッドセルには、フォールバックの三角形が割り当てられます。デフォルトの検索は非常に遅い (BruteForce) ですが、SpiralOut はより効率的ですが、空のフォールバック三角形が生成される可能性があります。
intClosestTriangleCalculationDepthSpiralOut 検索を拡張するためのグリッドセルの数。
boolEnableQuantum_XY有効にすると、ナビメッシュのベイキング中に頂点位置の Y と Z コンポーネントが反転され、XY 平面で生成されたナビメッシュをサポートします。
boolLinkErrorCorrectionベイキング中に三角形に最も近いナビメッシュリンクの位置を自動的に修正します。

MapNavMeshTriangle クラス

三角形は時計回りの巻き順を持つことが期待されます。
すべてのフィールドを記入する必要はありません。いくつかのフィールドは、レガシーナビメッシュ描画ツールにのみ必要です。

フィールド説明
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必須ではありません。

StartEnd、および CostOverride の型は、SDK 2.2 でそれぞれ FPVector3FP に置き換えられました。

フィールド説明
Vector3Startリンクの開始位置。同じナビメッシュ上になければなりません。
Vector3Endリンクの終了位置。同じナビメッシュ上になければなりません。
boolBidirectionalリンクが両方向から渡れるか。
floatCostOverride接続のコスト。
StringRegionIdこのリンクが所属する地域 ID。デフォルトは null です。
StringNameリンクの名前。 navmesh.Links[NavMeshPathfinder.CurrentLink()].Name によってクエリできます。

スニペット

C#

// シンプルなナビメッシュ 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