AIエージェントはどのようにして環境に関する情報にアクセスしますか?


9

これはちょっとささいな質問かもしれませんが、私はこれを理解するのに苦労しています。よろしくお願いします。

オブジェクト指向設計を使用したゲーム開発で、AIエージェントがアクションを実行するためにゲームの世界から必要な情報にアクセスする方法を理解したいと思います。

誰もが知っているように、ゲームではAIエージェントが「環境を認識」し、周囲で何が起こっているかに応じて行動する必要があることがよくあります。たとえば、エージェントは、プレイヤーが十分に近づいたらプレーヤーを追跡する、移動中に障害物を回避する(障害物回避のステアリング動作を使用する)などのようにプログラムされている場合があります。

私の問題は、それを行う方法がわからないことです。AIエージェントは、ゲームの世界について必要な情報にどのようにアクセスできますか?

可能なアプローチの1つは、エージェントが必要な情報をゲームの世界から直接要求することです。

GameWorldというクラスがあります。重要なゲームロジック(ゲームループ、衝突検出など)を処理し、ゲーム内のすべてのエンティティへの参照も保持します。

このクラスをシングルトンにすることができます。エージェントがゲームワールドからの情報を必要とするとき、それは単にGameWorldインスタンスから直接それを取得します。

たとえば、エージェントSeekはプレーヤーが近くにいるときにプレーヤーにプログラムされます。これを行うには、エージェントはプレーヤーの位置を取得する必要があります。そのため、直接要求することができますGameWorld.instance().getPlayerPosition()

エージェントは、ゲーム内のすべてのエンティティのリストを取得し、それをニーズに合わせて分析することもできます(どのエンティティが近くにあるか、またはその他の何かを把握するため): GameWorld.instance().getEntityList()

これは最も簡単な方法です。エージェントはGameWorldクラスに直接連絡して、必要な情報を取得します。しかし、これが私が知っている唯一のアプローチです。良いものはありますか?

経験豊富なゲーム開発者はこれをどのように設計しますか?「すべてのエンティティのリストを取得し、必要なものを探す」アプローチは単純ですか?AIエージェントがアクションを実行するために必要な情報にアクセスできるようにするには、どのようなアプローチとメカニズムがありますか?


GDCVaultサブスクリプションにアクセスできる場合は、2013年に「Hitman AIの生活、呼吸の世界のためのAIの作成」と呼ばれる優れた講演があり、詳細にAIナレッジモデルについて説明しています。
DMGregory

回答:


5

あなたが説明するのは、世界を照会する古典的な「プル」モデルです。ほとんどの場合、これは特に基本的なAI(ほとんど)を備えたゲームではかなりうまく機能します。ただし、マイナス面となる可能性があると考える必要のある点がいくつかあります。

  • あなたはおそらくダブルバッファしたいでしょう。主題のゲームプログラミングパターンを参照してください。常に世界から直接データを要求することにより、AIが呼び出される順序に結果が依存する奇妙な競合状態を取得できます。これがあなたのゲームにとって重要かどうかはあなたが決めることです。考えられる結果の1つは、「最初」または「最後」に行く人にゲームを偏らせ、マルチプレイヤーを不公平にすることです。

  • 多くの場合、特に特定のデータ構造に対しては、リクエストをバッチ処理する方がはるかに効率的です。ここで、障害物を検索したいすべてのAIエージェントに「クエリオブジェクト」を作成させ、それを中央の障害物シングルトンに登録します。次に、メインAIループの前に、すべてのクエリがデータ構造に対して実行されます。これにより、障害データ構造がよりキャッシュに保持されます。次に、AIの部分で、各エージェントはそのクエリ結果を処理しますが、これ以上直接作成することはできません。フレームの最後で、AIオブジェクトは新しい場所でクエリを更新するか、クエリを追加または削除します。これはデータ指向設計に似ています

    これは基本的に、クエリの結果をバッファに格納することでダブルバッファリングを行うことに注意してください。また、前にフレームのクエリを実行する必要があるかどうかを予測する必要もあります。これは「プッシュ」モデルです。これは、エージェントが(対応するクエリオブジェクトを作成することにより)関心のある更新の種類を宣言し、これらの更新がエージェントにプッシュされるためです。フレームのすべての結果を保存するのではなく、クエリオブジェクトにコールバックを含めることもできます。

  • 最後に、他の場所で詳しく説明されている継承ではなく、検索可能なオブジェクトのインターフェイスまたはコンポーネントを使用することをお勧めします。リストの繰り返し処理EntitiesのチェックはinstanceOf、おそらくかなり脆弱なコードのためのレシピですあなたは両方欲しい分StaticObjectMovingObjectしますHealable。(instanceOf選択した言語のインターフェースで機能しない限り。)


5

AIはコストがかかるため、多くの場合、パフォーマンスはアーキテクチャの推進要因です。

データアクセスモデルに関する懸念を和らげるために、ゲーム業界の内外でいくつかの異なるAIの例を検討してみましょう。ヒューマンナビゲーションから最も遠いものから、私たちにとって最も親しみのあるものまで機能します。

(各例では、単一のグローバルロジックの更新を想定しています。)

  • A *最短経路各AIは、パスファインディングの目的でマップの状態を計算します。A *では、各AIがパスファインドする必要がある(ローカル)環境全体をすでに認識している必要があるため、マップの障害物と空間(多くの場合、2Dブール配列)に関する情報を渡す必要があります。A *は、最短経路の開グラフ検索アルゴリズムであるダイクストラのアルゴリズムの特殊な形式です。このようなアプローチは、パスを表すリストを返します。AIは、目標に到達するまで、または再計算が必要になるまで(たとえば、マップの障害物の変更により)、各ステップで、このリストの次のノードを選択してステップします。この知識がなければ、現実的な最短経路は見つかりません。A *は、RTSゲームのAIが常にポイントAからポイントBに移動する方法を知っている理由です-パスが存在する場合。個々のAIの最短経路を再計算し、パスの有効性は、以前に移動した(および特定のパスをブロックした可能性がある)AIの位置に基づいているためです。A *がパスファインディング中にセル値を計算する反復プロセスは、数学的収束の1つです。最終的な結果は、嗅覚と視覚の混ざった感覚に似ていると言えるかもしれませんが、全体としては、私たちの考え方にはやや異質です。

  • 共同拡散ゲームでも見られますが、これはガスや粒子の拡散に基づく匂いの感覚に最もよく似ています。CDは、A *に見られるコストのかかる再処理の問題を解決します。代わりに、単一のマップ状態が保存され、すべてのAIの更新ごとに1回処理され、各AIが結果にアクセスして、それぞれの移動を行います。 。検索アルゴリズムによって返される単一のパス(セルのリスト)ではなくなりました。むしろ、各AIは処理後にマップを検査し、値が最も高い隣接セルに移動します。これは山登りと呼ばれます。それにもかかわらず、マップ処理フェーズはすでに地図情報に事前にアクセスできます。ここには、すべてのAIボディの場所も含まれています。したがって、マップはAIを参照し、AIはマップを参照します。

  • コンピュータービジョンとレイキャスティング+最短経路ローバーとドローンのロボット工学では、これはロボットが移動する空間の範囲を決定するための標準になりつつあります。これにより、視覚や音や触覚(視覚障害者や聴覚障害者の場合)と同じように、ロボットは環境の完全な体積モデルを構築でき、ロボットは最小の地形グラフ(ウェイポイントグラフのようなもの)に縮小できますA *)のゲームで使用され、最短パスアルゴリズムが適用される場合があります。この場合、「ビジョン」は現在の環境の手がかりを提供する可能性がありますが、それでも結果として最終的に目標へのパスを提供するグラフ検索。これは人間の考えに近いものです。寝室からキッチンに到達するには、リビングルームを通過する必要があります。私はすでにそれらを見て、それらのスペースとポータルを知っているという事実が、この計算された動きを可能にするものです。これは、ハードシリコンではなくソフトタンパク質に埋め込まれているにもかかわらず、最短経路アルゴリズムが適用されるグラフトポロジです。

したがって、最初の2つはすでに環境全体を把握していることに依存していることがわかります。これは、環境を最初から評価するコストの理由から、ゲームでは一般的です。明らかに、最後は最も強力です。この方法で装備されたロボット(または、たとえば、フレームごとに深度バッファーを読み取るゲームAI)は、事前の知識がなくても、どのような環境でも十分にナビゲートできます。おそらくご想像のとおり、これは上記の3つのアプローチの中ではるかにコストがかかります。ゲームでは、通常、AIごとにこれを行うことはできません。もちろん、2Dの方が3Dよりはるかに安価です。

建築上のポイント

上記で明らかになったのは、AIの正しいデータアクセスパターンを1つだけ想定することはできないということです。選択は、達成しようとしていることに依存します。GameWorldクラスに直接アクセスすることは絶対に標準です。単に世界の情報を提供するだけです。基本的にはデータモデルであり、それがデータモデルの目的です。これにはシングルトンで十分です。

「すべてのエンティティのリストを取得し、必要なものを探します」

それについて何も世間知らずです。素朴かもしれない唯一のものは、必要以上に多くのリスト反復を実行することです。衝突検出では、たとえば四分木を使用して検索スペースを削減することでこれを回避します。同様のメカニズムがAIにも適用できます。また、同じループを共有して複数のことを実行できる場合は、ブランチを使用するとコストがかかるため、そうすることができます。


回答ありがとうございます。私はゲーム開発の初心者なので、今は単純な「ゲームの世界からリストを取得する」アプローチに固執すると思います:) 1つの質問:私の質問では、GameWorldクラスをへの参照を含むクラスとして説明しましたすべてのゲームエンティティ、および重要な「エンジン」ロジックのほとんどが含まれています。メインゲームループ、衝突検出などです。これは基本的にゲームの「メインクラス」です。私の質問は、このアプローチはゲームで一般的ですか?「メインクラス」をお持ちですか?または、それをより小さなクラスに分けて、「エンティティデータベース」オブジェクトがポーリングできるように1つのクラスを用意する必要がありますか?
Aviv Cohn 2014

@Progどういたしまして。繰り返しになりますが、上記のAIアプローチ(または、その他の問題については)には、「ゲームの世界からリストを取得する」方法、形状、またはフォームがアーキテクチャ的に正しくないことを示唆するものはありませんAIのアーキテクチャは、AIのニーズに適合しなければなりません。ただし、そのロジックは、ご提案のとおり、より広範なアプリケーションアーキテクチャから切り離して(独自のクラスで)モジュール化し、カプセル化する必要があります。はい、そのような疑問が生じたら、サブシステムは常に別々のモジュールに分解する必要があります。あなたの指導原則はSRPである必要があります。
エンジニア

2

基本的に、情報を照会する方法は2つあります。

  1. 衝突やキャッシュを検出したためにAIStateが変化したとき。重要なオブジェクトへの参照が必要です。これにより、必要な参照がわかります。他のシステムでフレームごとに大規模な検索を実行する必要がある場合、複数の検索を実行する必要がないように、ピギーバックを推奨します。そのため、敵の「アラート」を作成するゾーンで「衝突」が検出され、メッセージ/イベントを送信します。メッセージ/イベントがまだない場合は、オブジェクトに登録し、ゲームの状態を、存在することに基づいてビジネスを行う状態に変更しますそのgamestate。変更を行うように指示する何らかのタイプのイベントが必要です。その情報を提供するために使用するコールバックに参照を渡します。これは、プレーヤーを処理する必要がある場合よりも拡張性があります。たぶん、あなたは敵に別の敵または他の何かを追いかけたいと思うかもしれません。この方法では、識別したタグを変更するだけで済みます。

  2. 次に、その情報を使用して、A *または他のアルゴリズムを使用してパスを取得するパス検索システムにクエリを実行するか、何らかのステアリング動作でそれを使用できます。たぶん両方の組み合わせか、何でも。基本的には両方の変換を使用して、ノードシステムまたはnavmeshにクエリを実行し、パスを取得できるようにする必要があります。ゲームワールドには、パスファインディング以外にも多くのものが含まれている可能性があります。クエリをパスファインディングのみに送信します。また、多くのクエリがある場合は、これらのバッチ処理が非常に集中的になる可能性があり、バッチ処理はパフォーマンスの向上に役立つため、おそらくバッチ処理が最適です。

    Transform* targetTransform = nullptr;
    EnemyAIState  AIState = EnemyAIState::Idle;
    void OnTriggerEnter(GameObject* go)
    {
       if(go->hasTag(TAG_PLAYER))
       {
       //Cache important information that will be needed during pursuit
       targetTransform = go->getComponent<Transform>();
       AIState = EnemyAIState::Pursue;
       }
    }
    
    void Update()
    {
       switch(AIState)
       {
          case EnemyAIState::Pursue:
           //Find position to move to
           Vector3 nextNode = PathSystem::Seek(
                              transform->position,targetTransform->position);
           /*Update the position towards the target by whatever speed the unit moves
             Depending on how robust your path system is you might want to raycast
             against obstacles it can't take into account or might clip the path.*/
            transform->Move((nextNode - transform->position).unitVector()*speed*Time::deltaTime());
            break;
        }
     }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.