衝突システムを使用する

Gameware Navigation には、空間内の 2 点間で、レベル ジオメトリに対して高パフォーマンス衝突テスト(レイキャスト)を行うための、軽量で複雑性の低いシステムが含まれています。

たとえば、このシステムを次の目的に使用できます。

多くの場合、これらのテストは物理サブシステムで実行されます。ただし、多くのゲーム(ほとんどの場合 MMO)では、ネットワークの通信ラグ、技術的な複雑性、ランタイム パフォーマンスのオーバーヘッドなどにより、本格的な物理システムの統合に問題や困難が生じる可能性があります。プロジェクトがこれに当てはまる場合で、それでもこれらの種類の衝突テストをゲーム レベルのジオメトリに対して行う必要があるときには、Gameware Navigation 衝突システムを活用できるかもしれません。

静的オブジェクトのみ

ビルトイン衝突システムは、地形用の NavData から分離されている事前生成されたデータに依存します。したがって、データ生成プロセス中に提供した地形の静的要素との衝突のみを検出することができます。実行時に追加した動的オブジェクトを地形の NavData に統合しても、それらのオブジェクトを考慮することはできません。

必要条件

衝突データを生成するために C++ NavData 生成 API を使用します。まだ NavData 生成 API をゲーム エディタ、レベル デザイン ツール、ビルド プロセスに統合していない場合は、「統合フェーズ 6: NavData 生成 API を使用する」を参照してください。

手順 1. 衝突データを生成する

NavData 生成コードの中で、地形内にある希望する各セクタの衝突データ生成をアクティブにする必要があります。

セクタの衝突データ生成をアクティブにするには:

セクタに NavData を生成する際には、そのセクタ用の NavData 生成システムに対して渡した三角形からそのセクタの衝突データも自動的に生成されます。各セクタのデータは .ColData ファイルでディスクに書き込まれます。そのセクタに対して生成された .NavData ファイルと同じ場所にあり、同じファイル名になります。

例:

// Create a sector...
Kaim::GeneratorSectorConfig sectorConfig(Kaim::KyGuid::GetDefaultGuid(), generationName);
Kaim::GeneratorSector* sector = env.m_generatorInputOutput.AddSector(sectorConfig);
...
// Activate collision data generation
sector->SetColDataBuildMode(Kaim::GenFlags::SECTOR_COLDATA_BUILD_ENABLED);
...
// Generate NavData and collision data
KyResult result = m_generator->Generate(m_generatorInputOutput);

オプション: より良いパフォーマンスのために高さフィールド(heightfield)を使用する

NavData 生成システムに地形の各セクタを構成する個々の三角形を提供する代わりに、高さフィールド(heightfield)と三角形化されたメッシュを提供することができます。

高さフィールド(heightfield)は、単純な単層の地形の高度を表すために最適化された方法です。地形メッシュを構成するすべての三角形を含める代わりに、高さフィールド(heightfield)は単一の原点と、原点の周囲の規則的なグリッドでサンプリングした高度値のセットから構成されています。このデータは完全に三角形化されたメッシュよりもはるかにコンパクトで複雑性が低いため、衝突テストでのランタイム メモリの消費を抑え、パフォーマンスを大きく向上させることができます。

高さフィールド(heightfield) を使用することの欠点は、高さフィールド高さフィールド(heightfield)は通常、グリッド内のサンプルの間隙で発生する高さの局所的な変化に対する精度が低いことです。このため、高さフィールドから生成された NavMesh、および高さフィールドに対して行われた衝突テストの精度が低くなる可能性があります。この精度の不足を補うために、地形の小さな特徴に対する高い忠実性が必要となる場所で、地形のより複雑な領域に対する三角形化されたメッシュも提供することができます。

効率性と精度の最善のバランスを得るには、高さフィールド(heightfield)を野原などの広い開けた領域を表すために使用し、三角形化されたメッシュを市街地などの高い精度が必要となる領域を表すために使用します。例:

セクタに高さフィールド(heightfield)とインデックス化されたメッシュを渡すには、地形ジオメトリを NavData 生成システムに渡すために NavData 生成パイプラインで使用する GeneratorInputProducer 内の GeneratorInputProducer::Produce() の実装を更新する必要があります。

セクタに高さフィールド(heightfield)を提供するには:

  • セクタに提供する各高さフィールド(heightfield)で、GeneratorInputProducerKaim::Heightfield クラスのインスタンスを作成し、インスタンスを現在のセクタのデータで設定し、ClientInputConsumer::ConsumeHeightField() の呼び出しでポインタを渡す必要があります。

セクタに三角形化されたメッシュを提供するには:

  • セクタに提供する各メッシュで、GeneratorInputProducerKaim::IndexedMesh クラスのインスタンスを作成し、インスタンスを現在のセクタのデータで設定し、ClientInputConsumer::ConsumeIndexedMesh() の呼び出しでポインタを渡す必要があります。

このアプローチを使用すると、NavData が高さフィールドや三角形化されたメッシュを基に生成されることに注意してください。そのため、個々の三角形を直接 ClientInputConsumer に提供する必要はありません。

手順 2. ランタイム World をセットアップする

World は、World::m_collisionWorld クラス メンバーの中に CollisionWorld クラスのインスタンスを保持します。このクラスはメモリにロードした衝突データを集約したり、そのデータに対して行った衝突テストを管理する役割を持ちます。これは、World を初期化した後に渡す必要のある ICollisionInterface を実装するクラスのインスタンスのメソッドを呼び出すことで行われます。

Gameware Navigation には、オープンソースの Bullet の物理/衝突テスト システムに依存する ICollisionInterface の実装が含まれています。詳細については、www.bulletphysics.org を参照してください。

デフォルトの ICollisionInterface 実装を使用するには:

  1. integration¥gwnavruntimeglue¥bulletcollisioninterface フォルダ内のすべてのヘッダ ファイルおよびソース ファイルをプロジェクトに追加し、そのフォルダでインクルード ファイルを検索するようにコンパイラを設定します。
  2. ランタイム初期化コードで、CollisionInterface クラスの新しいインスタンスを作成します。
  3. World を作成した後に、CollisionWorld::SetCollisionInterface() メソッドの呼び出しでインタフェース オブジェクトを渡して、World::m_collisionWorld がインタフェース オブジェクトを使用するように設定します。

例:

m_navigationWorld = *KY_NEW Kaim::World(databaseCount);
Kaim::Ptr<Kaim::ICollisionInterface> visInterface = *KY_NEW CollisionInterface(m_navigationWorld);
m_navigationWorld->m_collisionWorld->SetCollisionInterface(visInterface);

手順 3. 実行時に衝突データをロードする

地形に対して作成した衝突データに対して衝突テストを行う必要がある場合、衝突データをメモリにロードして衝突ワールドに追加する必要があります。これは通常、そのセクタに必要な NavData と他の種類のデータをメモリーにストリーミングしたり、メモリーからストリーミングするのと同時に地形の各セクタに対して行います。このプロセスは NavData を Database にストリーミングしたり、 Database からストリーミングするのと非常に似ています。

実行時に衝突ワールドに衝突データをロードするには:

  1. 新しい CollisionData オブジェクトを作成します。
  2. 衝突データを CollisionData オブジェクトにロードします。NavData オブジェクトに事前に生成された NavData を入れるために使用するアプローチとまったく同じアプローチを使用できます。

    • 直接ディスク上の .ColData ファイル、またはファイル ストリームからデータをロードするために CollisionData::Load() を呼び出しします。
    • 衝突データをメモリにロードします。そして、読み込んだ衝突データを入れたバッファから CollisionData オブジェクトを初期化する為にCollisionData::LoadFromMemory() を呼び出します。このメモリ バッファはファイルから自分で読み取るか、「統合フェーズ 6a: NavData をアセット パイプラインに統合する」で説明しているようにセクタに関連するその他のアセットにパッケージ化することで取得できます。

    どちらのメソッドも同等です。アセット パイプラインにより適したメソッドを使用できます。

  3. World::m_collisionWorld に保持されている CollisionWorld オブジェクトを取得し、その CollisionWorld::AddCollisionData() メソッドを呼び出して、CollisionData オブジェクトのポインタを渡します。

例:

Kaim::Ptr<Kaim::CollisionData> colData = *KY_NEW Kaim::CollisionData;
if (Kaim::Result::Fail(colData->Load(GetAbsoluteInputFileName(fileName).c_str(), &m_fileOpener)))
		return KY_NULL;

GetWorld()->m_collisionWorld->AddCollisionData(colData);

衝突ワールドから衝突データを削除するには:

例:

GetWorld()->m_collisionWorld->RemoveCollisionData(colData);

手順 4. 衝突クエリーを実行する

World 内にロードした衝突データに対して衝突テストを実行するには、CollisionRayCastQuery クラスのインスタンスを設定して起動します。

たとえば、次のコードはポイント A とポイント B の間の直線パスに沿って衝突をテストします。

Kaim::CollisionRayCastQuery query;
query.BindToWorld(m_navigationWorld);
query.Initialize(pointA, pointB);
query.PerformQuery();

if (query.GetResult() == Kaim::RayHit)
    ... // A collision was found
if (query.GetResult() == Kaim::RayDidNotHit)
    ... // No collision was detected

World によって保持されたキュー(またはフレームごとの最大時間の割り当てを使用して作成および設定したカスタム キュー)内で CollisionRayCastQuery を非同期的で実行するためにクエリー システムによって提供されたメカニズムを利用することもできます。ゲームでいくつかの衝突テストを同じフレームで要求する必要がある可能性がある場合は、これらのアプローチが CPU のピークを回避するために役立ちます。

詳細は、「クエリ システムを使用する」を参照してください。