NavTag を禁止、回避、優先する

NavTag を使用してカスタム データで地形の領域をタグ付けする場合、複数のキャラクタが同じ Database を(つまり、同じ NavData のセットを)使用している場合でも、各キャラクタに対してどの NavTag が有効な歩行可能な地形として認識されるかを決定することができます。また、異なる Bot が異なる NavTag をトラバースするのを優先させたり、回避させたりする範囲をカスタマイズすることもできます。

たとえば、次のイメージは、道路を横断する際に考えられる 3 つのパスを示しています。

  1. パス番号 1 では、紫色の NavTag 上の移動が禁止されています。これにより、横断歩道で道路を横断することが強制されます。
  2. パス番号 2 では、紫色の NavTag は移動可能とみなされ、タグのないエリアをトラバースするのと同じコストがかかります。これにより、開始地点から目的地までの真っすぐなパスが使用できます。
  3. パス番号 3 では、紫色の NavTag は移動可能とみなされますが、トラバースのコストはタグのない青色のエリアの 2 倍です。これにより、ゾーン内の移動を最小限にする最終的なパスが推奨されます。道路の広い部分を横断する代わりに、横断歩道を使用したときと比較してもショートカットとなる横断する幅が狭いパスを見つけます。

手順 1. 地形にタグ付けする

データ生成フェーズ中に、またはゲームの中で動的に地形に NavTag を適用する方法の詳細については、「カスタム データでタグ付けする」を参照してください。

手順 2. TraverseLogic を作成する

ユーザ独自のパスで NavTag を禁止、優先、回避するには、カスタム TraverseLogic クラスを作成する必要があります。このクラスは次の目的で使用されます。

また、NavMesh に対してその他の種類のクエリーを実行するたびに、NavTag をテストするためにこの同じ TraverseLogic クラスを使用することができます。たとえば、RayCastQuery を実行して 2 点間の直線パス上に障害物が存在するかどうかを検出する場合、作成したクラスのインスタンスを使って呼び出すクエリを設定することができます。そのクラスがどの NavTag を通行可能とみなすかを決定するのに使われます。

TraverseLogic の 3 つのインタフェースは次のとおりです。デフォルトの実装では、3 つの異なるレベルの複雑度が用意されており、要件に応じて適切な複雑度を選択することができます。

TraverseLogic の 1 つがテンプレート パラメータとして NavigationProfile またはクエリに渡されると、対応するコードが自動的に生成されます。「手順 3. TraverseLogic を設定する」を参照してください。

三角形のコストの乗数を計算、格納する

TraverseLogicWithCostPerTriangle 内では、たった一度の PathFinderQuery の間に三角形のコストの乗数が何度も要求されます。不適切な実装によるパフォーマンスの問題を回避するため、一部の三角形のコスト乗数を計算および格納するために TriangleCostMap クラスが使用されます。このクラスはクエリによってアクセスが可能です。ITriangleCostMap から派生するユーザ独自の TriangleCostMap クラスを作成する必要があります。これには純粋な仮想 Recompute() 関数が 1 つ含まれます。

ITriangleCostMap は、使用する前に、BindToDatabase () 関数を使用してデータベースにバインドする必要があります。データベースは、動的な NavMesh などのデータの三角形化に変更があった場合、内部的に Recompute() 関数を呼び出すことができます。この関数は、コストの乗数を更新するためにクライアントによって呼び出すことができますが、頻繁に実行してはいけません。Recompute() 内で三角形のコストの乗数を自動的に初期化および設定するために、次のヘルパー関数を呼び出すことができます。 ITriangleCostMap::InitAndBrowseAllTrianglesInBox3f() 、および ITriangleCostMap::InitAndPropagateInTrianglesFromPosInVolume() です。詳細については、itrianglecostmap.h を参照してください。

三角形のコストの設定方法は独自に作成することができますが、三角形に対して SetCostMultiplier() 関数を呼び出してコストの乗数(デフォルトのコスト乗数は 1.f)を変更する前に InitCostMapForBox3f() 関数を呼び出すように作成する必要があります。

対象となるその三角形に対してパス ファインディングを行っている Bot がいる場合には、その三角形のコストを変更しないようにする必要があります。TriangleCostMap を使用する Bot の非同期パス計算はキャンセルすることをお勧めします。TriangleCostMap を頻繁に更新しないようにする必要があります。頻繁に更新を行うと、Bot が 2 つの更新の間にパスを検索できるだけの時間が不足する可能性があります。次のコード例は、パスの計算をキャンセルするために Recompute() 関数で実行できる内容を示しています。

for (KyUInt32 i = 0; i < gameWorld->GetBots().GetCount(); ++i)
    {
        GameBot* gamebot = gameWorld->GetBots()[i];
        Kaim::Bot* navBot = gamebot->GetBot();
        if (navBot->IsComputingNewPath())
            navBot->CancelAsyncPathComputation();
    }

更新した TriangleCostMap と密接に関係しているパスを追従し続けるために、既にパスを追従している Bot でパス計算を再起動する必要があるかどうかを指定することができます。

また、独自の TraverseLogic を設定しない場合にデフォルトで使用される DefaultTraverseLogic クラスも参照してください。DefaultTraverseLogic または SimpleTraverseLogic からカスタム クラスを直接派生させる場合は、目的の動作を実現するためにオーバーライドのメソッドの実装だけを行う必要があります。

NavTag のトランジションをコントロールする

デフォルトでは、DefaultTraverseLogic クラスまたは SimpleTraverseLogic クラスは禁止された NavTag を通過するすべての動作を防ぎます。

ただし、一部の特定のケースでは、NavTag を横断するナビゲーションを 1 方向のみ禁止したり、特定の他の NavTag からのナビゲーションのみを禁止する必要がある場合もあります。この方法では、NavTag で覆われる エリア 全体の移動を許可し、NavTag 境界 を横断する移動を禁止します。

この場合、次のことを行う必要があります。

  • SimpleTraverseLogic からクラスを直接派生させ、テンプレート パラメータとして Kaim::LogicDoUseCanEnterNavTag を渡します。例:
    class MyTraverseLogicClass : public Kaim::SimpleTraverseLogic<Kaim::LogicDoUseCanEnterNavTag>
    {
        ...
    };

    または、いずれかの既存のクラスから派生させるのではなく、独自の TraverseLogic クラスを最初から作成する場合は、次の行をクラスに含めます。

    typedef Kaim::LogicDoUseCanEnterNavTag CanEnterNavTagMode;
  • NavTag を横断する移動を許可するように CanTraverse() メソッドの実装を設定します。
  • 前の NavTag からの NavTag へのトランジションを許可するかどうかを判定するように CanEnterNavTag() メソッドの実装を設定します。

最良のパフォーマンスのためには、ゲームプレイで使用する特定のケースがある場合には、NavTag のトランジションを確認するために TraverseLogic のみを設定する必要があります。

ユーザ データ

TraverseLogic インタフェースのすべてのメソッドは静的です。そのため特定のオブジェクトまたは状態には関連付けられていません。これにより、実行時に使用する Bot およびクエリのクラス インスタンスを作成する必要がなく、ランタイム メモリが節約されます。

ただし、NavTag を禁止する必要があるかどうかについてや、さまざまな状況下でさまざまな NavTag に何のコスト乗数を割り当てるべきかについて TraverseLogic に優れた決定をさせるには、オブジェクトの特定のインスタンスに関連付けられた何らかのデータを使用する必要がある場合もあります。このため、TraverseLogic メソッドはすべて、void ポインタを受け入れます。TraverseLogic を使用するためにテンプレート化されている各クラス(最も顕著なのは Bot クラスと、AStarQueryRayCastQuery などのクエリー クラス)には、TraverseLogic インタフェースのメソッドを呼び出す必要があるたびに Bot または Query が渡すユーザ データを設定および取得するために使用できるアクセサが用意されています。例:

Bot::SetBotTraverseLogicUserData()
Bot::GetBotTraverseLogicUserData()

IQuery::SetTraverseLogicUserData()
IQuery::GetTraverseLogicUserData()

手順 3. TraverseLogic を設定する

Bot を設定するには:

  1. NavigationProfile クラスから派生する独自のクラスを作成し、NavigationProfile のテンプレート パラメータとして TraverseLogic クラスの名前を渡します。必要がない場合には、NavigationProfile クラスの仮想メソッドをオーバーライドする必要はありません。次に例を示します。
    class MyNavigationProfile : public Kaim::NavigationProfile<MyTraverseLogicClass>
    {
    };
  2. 新しい NavigationProfile を使用するように WorldBots を設定します。詳細は、「パス フォローイングをカスタマイズする」を参照してください。

自分で処理するクエリーを設定するには:

制限事項

コストの計算方法

各パス セグメントのコストは、経路に障害物がないことをテストするために AStarQuery またはパス フォローイング システムによって内部的に実行される RayCanGoQuery によって個別に計算されます。RayCanGoQuery によってテストされる線分セグメントが NavTag 境界を交差するたびに、クエリはその境界点を線分セグメントに対して垂直に投影します。その後、対応する NavTag に割り当てられているコストの乗数によって、その線分セグメントに沿った各サブセグメントの距離を乗算します。

たとえば、次のシナリオでは、RayCanGoQuery によってテストされる線分セグメントは、NavMesh のトライアングルを通って伝播するため、いくつかの異なる NavTag と交差しています。

総コストは次のように計算されます。

                        ( Distance(a) * CostMultiplier(blue) )
                    +   ( Distance(b) * CostMultiplier(grey) )
                    +   ( Distance(c) * CostMultiplier(blue) )
                    + ( Distance(d) * CostMultiplier(purple) )
                    +   ( Distance(e) * CostMultiplier(blue) )
                    +   ( Distance(f) * CostMultiplier(grey) )
                    +   ( Distance(g) * CostMultiplier(blue) ) 

パス全体のコストを決定する場合、パスの各セグメントはこの方法でテストされ、総コストはすべてのセグメントのコストの合計となります。

パフォーマンス上の理由から、コスト測定のこの方法では NavMesh のトライアングルを通るパスが実際に進む正確な距離を使用せず、そのパスの追従中にキャラクタが移動する正確な距離も使用しません。しかし、この近似値はゲームプレイに必要なエフェクトを生成する一連のコストの乗数を見つけることができるような、十分に近いものであるはずです。

RayCanGoQuery を独自のコードで直接実行する場合は、テストする線分のコストを計算するように設定することができます。RayCanGoQuery::SetComputeCostMode() メソッドを呼び出して QUERY_COMPUTE_COST_ALONG_3DAXIS を渡し、NavTag の移動可能性とコストの乗数を決定するために使用する必要がある TraverseLogic を使ってクエリーを設定するようにします。クエリを実行した後は、RayCanGoQuery::GetComputedCost() を呼び出すことによってコストを取得できます。

チュートリアル サンプル

カスタム TraverseLogic クラスの使い方を説明している実用的なコード サンプルに関しては、Tutorial_NavTag.cpp ファイルを参照してください。これには、A* クエリーによって生成されるパスに影響を与える為のものや、パス フォローイング中に特定の NavTag を禁止する述部としてのものが含まれています。