パス フォローイングをカスタマイズする

キャラクタのパス フォローイング システムをカスタマイズするには、単純なもの(クラス メソッドの呼び出しなど)から複雑なもの(コア クラスの再実装)まで、さまざまな方法を使用できます。このトピックおよびこのセクションの他のトピックでは、パス フォローイング システムのさまざまな要素の動作をコントロールするための最も一般的なオプションと戦略の概要について説明します。

Bot 設定パラメータとクラス メソッドをカスタマイズする

各 Bot は、パスを検出して追従する方法をコントロールする幅広い設定パラメータを受け入れます。

パラメータを設定する

Bot 設定パラメータは、Bot::Init() の呼び出しの初期化時に指定する BotConfig オブジェクトで設定することができます。このアプローチによって、複数のゲーム キャラクタを同じ設定パラメータのセットで設定するのに使用できる、単一の設定オブジェクトを作成および設定することができます。

BotConfig クラスはいくつかの単純なデータ メンバを含み、その他のパラメータを機能カテゴリ別に下位の設定オブジェクトへとグループ化します。

例:

Kaim::BotConfig botConfig;
botConfig.m_enableAvoidance = true;
botConfig.m_shortcutTrajectoryConfig.m_maxDistanceFromBot = 20.0f;
...

Kaim::Ptr<Kaim::Bot> bot = *KY_NEW Kaim::Bot();
bot->Init(botInitConfig, botConfig);

BotConfig のすべての設定パラメータは、Bot API のメソッドを呼び出すことにより、各キャラクタに対して動的に設定することもできます。

BotConfig の単純なデータ メンバは、Bot クラスのメソッドを通じて直接公開されます。BotConfig によって公開される下位の設定オブジェクトについては、Bot クラスが現在のインスタンスを取得したり設定するアクセサ メソッドも公開します。

例:

bot->SetEnableAvoidance(true);

Kaim::ShortcutTrajectoryConfig shortcutConfig;
shortcutConfig.SetDefaults();
shortcutConfig.m_maxDistanceFromBot = 20.0f;
bot->SetShortcutTrajectoryConfig(shortcutConfig);

物理的な寸法

  • Bot の半径は、BotConfig::m_radius によって、または Bot::SetRadius() を呼び出すことによって設定できます。ここで設定する値は、ダイナミック回避システムおよび Navigation Lab のビジュアル デバッグのみを目的にして、キャラクタの幅を定義することに注意してください。パス ファインディングとパス フォローイングにおいて、キャラクタの半径は Botによって使用される NavMesh を生成するために使用される半径になると想定されます。ゲーム キャラクタの実際の半径は、常に NavMesh を生成するために使用される値に近いものにする必要があります。そうでないと、キャラクタが静的ジオメトリにぶつかったり突き抜けてしまう問題が発生する可能性があります。
  • Bot の高さは、BotConfig::m_height によって、または Bot::SetHeight() を呼び出すことによって設定できます。この値は、Navigation Lab でのビジュアル デバッグにのみ使用されます。

パス ファインディングの設定

  • PathFinderConfig クラスは、BotConfig::m_pathFinderConfig によって、または Bot::SetPathFinderConfig() を呼び出すことによって設定でき、AStarQuery によって公開されるいくつかのオプションへのアクセスを提供します。詳細は、「AStarQuery オプション」を参照してください。

    ただし、クエリーは PathFinderConfig で公開される設定オプションよりも多くのものをサポートしています。これらその他のオプションのいずれかを使用する場合、または 1 回限りのパス計算中に異なるオプションを設定する場合は、クエリーを直接設定する必要があります。「静的パスを取得する」の「上級」のパス計算方法を参照してください。

  • Bot がチャネルとスプラインを使用して軌道を生成する場合は(次を参照)、ChannelComputerConfig クラスを使用して、各パスに対して生成されるチャネル データの特性を制御する一部のパラメータ(たとえばチャネルの推奨幅)を設定することもできます。これは BotConfig::m_channelComputerConfig によって、または Bot::SetChannelComputerConfig() を呼び出すことによって設定できます。

パス進行の設定

パスに沿った Bot の進行状況をパス フォローイング システムがどのように監視するかを決めるいくつかのパラメータを設定する為にPathProgressConfig クラスを使用することができます。

たとえば、チェック ポイント システムを広範囲に使用する場合は、PathProgressConfig::m_checkPointRadius 値をカスタマイズすることができますが、この値によって Bot がチェック ポイントにどの程度近づくとそのチェック ポイントを通過して先に進むことができるのかが決定されます。「チェック ポイントを使用する」を参照してください。

PathProgressConfig は、BotConfig::m_pathProgressConfig によって、または Bot::SetPathProgressConfig() を呼び出すことによって設定できます。

軌道計算の設定

  • キャラクタで使用する軌道計算システムは、BotConfig::m_trajectoryMode によって、または Bot::SetTrajectoryMode() を呼び出すことによって選択できます。ショートカット システムからチャネル システムに切り替える場合は、チャネルの軌道計算に必要となるチャネル データを生成するために、新しいパス再計算を起動する必要があることに注意してください。チャネルの軌道システムからショートカット システムへの切り替えは、ただちに有効になります。
  • ショートカット軌道システムをコントロールするためのパラメータは、ShortcutTrajectoryConfig クラスに集められます。これは、BotConfig::m_shortcutTrajectoryConfig によって、または Bot::SetShortcutTrajectoryConfig() を呼び出すことによって設定できます。

    たとえば、Bot が新しいショートカットをチェックする頻度、ショートカットを離すことができる距離、パスに沿ってチェックする候補ポイントの間隔などをコントロールできます。

  • チャネル軌道システムをコントロールするためのパラメータは、SplineTrajectoryConfig クラスに集められます。これは、BotConfig::m_splineTrajectoryConfig によって、または Bot::SetSplineTrajectoryConfig() を呼び出すことによって設定できます。

    たとえば、Bot がコーナー上のチャネル境界にどのくらい近づくことができるか、どのくらいの頻度でスプラインを再計算する必要があるか、各スプラインの望ましい長さなどをコントロールできます。

  • どちらの軌道計算システムも LocomotionModelConfig クラスからのパラメータを使用して、現在のターゲット ポイントまたは追従する現在のスプラインに基づく Bot の最終速度の計算方法を決定します。このクラスは、BotConfig::m_locomotionModelConfig によって、または Bot::SetLocomotionModelConfig() を呼び出すことによって設定できます。

    キャラクタの望ましい最大速度(LocomotionModelConfig::m_maxDesiredLinearSpeed)は、Bot クラスのショートカット アクセサ Bot::SetMaxDesiredLinearSpeed() および Bot::GetMaxDesiredLinearSpeed() を介しても公開されます。

  • どちらの軌道計算システムでも、最後の計算の後でキャラクタが最小距離のしきい値を超えて移動しなかった場合は、軌道の再計算をスキップします。このしきい値は、Bot::SetTrajectoryMinimalMoveDistance()Bot:: GetTrajectoryMinimalMoveDistance() を使用して Bot クラスで設定できます。

ダイナミック回避の設定

  • キャラクタのダイナミック回避は、BotConfig::m_enableAvoidance によって、または Bot::SetEnableAvoidance() を呼び出すことによって有効または無効にすることができます。デフォルトの Trajectory クラスによって使用されるダイナミック回避システムは、キャラクタが近くにいる他のキャラクタや動的な障害物との衝突を回避するのに役立ちます。ただし、フレームごとのパス フォローイング処理の計算負荷が増加します。群衆が非常に大規模で、時折発生する衝突に気づかないほど LOD が低い場合は、回避システム全体を完全に無効にしてもかまいません。
  • ColliderCollectorConfig クラスには、他のキャラクタや動的な障害物が潜在的なコライダであるとみなされる距離およびシステムが潜在的なコライダのリストを更新する頻度をコントロールするパラメータが含まれます。BotConfig::m_colliderCollectorConfig によって、または Bot::SetColliderCollectorConfig() を呼び出すことによって設定できます。
  • AvoidanceConfig クラスには、潜在的な衝突を検出したときのキャラクタの動作、および衝突を回避するように速度を変更する方法をコントロールするパラメータが含まれます。BotConfig::m_avoidanceConfig によって、または Bot::SetAvoidanceConfig() を呼び出すことによって設定できます。

NavigationProfile をカスタマイズする

NavigationProfile は、パス計算中およびパス フォローイング中に使用されるクラスのインスタンスを提供するオブジェクト ファクトリです。主な目的は、次の 3 つです。

World は、初期化時にデフォルトの NavigationProfile を作成し、維持します。これは、SDK で提供されるデフォルトのすべてのパス ファインディングおよびパス フォローイング クラスのインスタンスへのアクセスを提供します。すべての Bot は、別途指定しない限り、このデフォルトの NavigationProfile を使用するように初期化時に自動的に設定されます。

デフォルトの NavigationProfile は、何も設定しなくても完全に機能しますが、提供される一連のデフォルト オブジェクトは高度なカスタマイズが可能です(詳細については、前のセクションを参照)。このデフォルト プロファイルは、プロジェクトで問題なく使用し続けることができます。NavigationProfile が提供する 1 つまたは複数のクラスのデフォルトの実装をオーバーライドする特別な必要性がある場合にのみ、別の NavigationProfile を使用してください。

NavigationProfile でユーザによるカスタマイズが必要になる一般的なケースは次のとおりです。

これらのすべての場合で、カスタム実装を提供するために NavigationProfile のカスタム クラスを設定する必要があります。

カスタム NavigationProfile を設定する

NavigationProfiles は、World によって一元管理されます。新しい NavigationProfile を設定するたびに、World は一意の ID をそれに割り当てます。その他のオブジェクト(通常は Bot およびパス ファインディング クエリー)をその他のオブジェクトが使わなければならないプロファイルで設定するために、この ID 値を使用します。

デフォルト NavigationProfile の一意の ID は常に 0(ゼロ)であることに注意してください。

Bot のカスタム NavigationProfile を設定するには:

  1. NavigationProfile で提供したいインタフェースのカスタム実装、または TraverseLogic インタフェースのカスタム実装を記述します。
  2. NavigationProfile から派生する新しいクラスを記述し、基本クラスをこの新しいクラスで使用したい TraverseLogic のクラスのテンプレートにします。禁止および優先 NavTag をコントロールする場合は、カスタム クラスにすることができます。それ以外の場合は、SDK で提供されるデフォルトの SimpleTraverseLogic クラスである必要があります。
  3. NavigationProfile で提供される任意のインタフェースのカスタム実装を使用する場合は、カスタム クラスのオブジェクトへのポインタを返すように適切な仮想メソッドを再実装します。
  4. World を初期化したら、NavigationProfile クラスのインスタンスを作成し、World::AddNavigationProfile() を呼び出してそのインスタンスへのポインタを渡します。このメソッドは、プロファイルに割り当てられている一意の ID を返します。この ID 値を覚えておいてください。
  5. Bot が新しいプロファイルを使用するようにするには、BotInitConfig::m_startNewPathNavigationProfileId によって、または Bot::SetNewPathNavigationProfileId() を呼び出すことによって設定できます。Bot::ComputeNewPathToDestination() を呼び出してパス計算をリクエストするときは、常にカスタム プロファイルが使用されるようになります。「静的パスを取得する」の「基本」のパス計算方法も参照してください。

    ただし、そのページの「上級」のパス計算方法で説明されているとおり、カスタムのパス ファインディング クエリーまたは独自の事前計画済みパスを使用して Bot を設定する場合、Bot はそのパスに追従している間はクエリーや Path オブジェクトに設定されたプロファイル ID を自動的に使用します。Bot でカスタム プロファイルを使用する場合は、IPathFinderQuery::SetNavigationProfileId() または Path::SetNavigationProfileId() メソッドを呼び出して、カスタム プロファイルの ID を使用してカスタム クエリーまたは Path オブジェクトを設定する必要もあります。

例:

// The NavigationProfile in this example uses a custom TraverseLogic and a custom IPathEventListObserver.

class MyTraverseLogic : public Kaim::DefaultTraverseLogic
{
public:
    static KY_INLINE bool CanTraverseNavTriangle(void* userData, const Kaim::NavTriangleRawPtr& triangleRawPtr,
        const Kaim::NavTag& navTag, KyFloat32* costMultiplier)
    { ... }
};

class MyPathEventListener : public Kaim::IPathEventListObserver
{
public:
    virtual void OnPathEventListBuildingStageDone(Kaim::Bot* bot, Kaim::PathEventList& pathEventList, KyUInt32 firstIndexOfNewEvent, FirstIntervalStatus firstIntervalStatus)
    { ... }
    virtual void OnPathEventListDestroy(Kaim::Bot* bot, Kaim::PathEventList& pathEventList, DestructionPurpose destructionPurpose)
    { ... }
};

class MyNavigationProfile : public Kaim::NavigationProfile<MyTraverseLogic>
{
public:
    virtual Kaim::Ptr<Kaim::IPathEventListObserver> GetSharedPathEventListObserver()
    {
        if (m_myPathEventListener == KY_NULL)
            m_myPathEventListener = *KY_NEW MyPathEventListener();
        return m_myPathEventListener;
    }

public:
    Kaim::Ptr<MyPathEventListener> m_myPathEventListener;
};

...

// in your World initialization code:
const KyUInt32 databaseCount = 1;
m_world = *KY_NEW Kaim::World(databaseCount);

Kaim::Ptr<MyNavigationProfile> myNavigationProfile = *KY_NEW MyNavigationProfile();
KyUInt32 navigationProfileId = m_world->AddNavigationProfile(myNavigationProfile);
...

// in your Bot initialization code:

m_navBot = *KY_NEW Kaim::Bot;

Kaim::BotInitConfig botInitConfig;
botInitConfig.m_database = m_world->GetDatabase(0);
botInitConfig.m_startPosition = m_position;
botInitConfig.m_startNewPathNavigationProfileId = navigationProfileId;
m_navBot->Init(botInitConfig);

// We add the Bot to our Database.
m_navBot->AddToDatabase();
...

Bot のコントロールを一時的に取得する

特定のアニメーションを再生したり(はしごを登る、ジャンプするなど)、カットシーンを再生したりするために、キャラクタの動きを一時的に完全にコントロールしたいことがあります。

コントロールを一時的に取得するために、パスへの追従だけを通常どおりに続けて、パス フォローイング システムによって計算された速度は無視することができます。

ただし、Bot::SetDoValidateCheckPoint()Bot::SetDoComputeTrajectory()両方 を呼び出して、一時的にパス フォローイングを無効にすることをお勧めします。キャラクタの動作を完全にコントロールする必要がなくなったとき、パス フォローイングを再度アクティブにするには、これらを再び呼び出します。例:

void MyJumpObject::ManageTraversing()
{
    // Take control
    m_navBot->SetDoValidateCheckPoint(false);
    m_navBot->SetDoComputeTrajectory(false);
  
    ... // manage playing the jump animation

    if (animationIsFinished)
    {
        // Release control
        m_navBot->SetDoValidateCheckPoint(true);
        m_navBot->SetDoComputeTrajectory(true);
    }
}

この方法でパス フォローイング システムが無効になっている間、Navigation Lab でのビジュアル デバッグでは Bot がデフォルトの黄ではなく青でレンダリングされることに注意してください。

また、キャラクタの動作のコントロールを保持している時間、および動かした距離によっては、コントロールを解放するときに現在のターゲット ポイントを強制的にパスの前方にする必要がある場合があることにも注意してください。「スマート オブジェクトを作成する」を参照してください。