目的地が通過できない場合、どうすればA *をより速く終了できますか?


31

A *( "A star")経路探索アルゴリズムを使用する単純なタイルベースの2Dゲームを作成しています。すべて正常に動作していますが、検索にパフォーマンスの問題があります。簡単に言えば、通行不可能なタイルをクリックすると、アルゴリズムはどうやらマップ全体を通過して、通行不可能なタイルへのルートを見つけます。

どうすればこれを回避できますか?画面領域へのパスファインディングを減らすことができたと思いますが、おそらくここで明らかな何かを見逃していますか?


2
どのタイルが通過できないかを知っていますか、それともAStarアルゴリズムの結果として知っていますか?
user000user

ナビゲーショングラフをどのように保存していますか?
アンコ

トラバースされたノードをリストに保存する場合、バイナリヒープを使用して速度を向上させることができます。
ChrisC

単に遅すぎる場合、一連の最適化を提案する必要があります-または、検索を完全に回避しようとしていますか?
スティーブン

1
この質問はたぶんコンピュータサイエンスにより適していたでしょう。
ラファエル

回答:


45

失敗したパスを完全に引き起こす検索を回避するためのいくつかのアイデア:

アイランドID

A *検索を効果的に高速で完了する最も安価な方法の1つは、検索をまったく行わないことです。すべてのエージェントがエリアを本当に通行できない場合は、ロード時(またはパイプライン内)に一意のアイランドIDを各エリアに塗りつぶします。場合は、チェックを経路探索すると島IDパスの起点は、一致した島のIDを送信先の。一致しない場合、検索を実行するポイントはありません。2つのポイントは、別個の接続されていない島にあります。これは、すべてのエージェントに対して真に通過できないノードがある場合にのみ役立ちます。

上限を制限する

検索できるノードの最大数の上限を制限します。これは、通過できない検索を永久に実行するのに役立ちますが、非常に長い一部の通過可能な検索が失われる可能性があることを意味します。この数は調整する必要があり、実際には問題を解決しませんが、長時間の検索に関連するコストを軽減します。

時間がかかりすぎていることがわかっている場合は、次の手法が役立ちます。

非同期にし、反復を制限する

検索が別のスレッドで実行されるか、フレームごとに少しずつ実行されるので、ゲームが検索を待って停止することはありません。検索が終了するのを待っている間に、キャラクターが頭を掻いたり足を踏みつけたりするのをアニメーション化します。これを効果的に行うには、検索の状態を別のオブジェクトとして保持し、複数の状態が存在できるようにします。パスが要求されたら、空き状態オブジェクトを取得し、アクティブ状態オブジェクトのキューに追加します。パスファインディングの更新で、アクティブなアイテムをキューの先頭から引き出し、A。が完了するか、B。反復の制限が実行されるまでA *を実行します。完了したら、状態オブジェクトを空き状態オブジェクトのリストに戻します。完了していない場合は、「アクティブな検索」の最後に配置し、次の検索に移動します。

適切なデータ構造を選択する

正しいデータ構造を使用していることを確認してください。StateObjectの仕組みを次に示します。すべてのノードは、パフォーマンス上の理由から、有限数(1024または2048など)に事前に割り当てられています。ノードの割り当てを高速化するノードプールを使用します。また、データ構造にポインターの代わりにインデックスを格納することもできます。これは、u16(または255個の最大ノードがある場合はu8、一部のゲームで行う)です。経路探索では、オープンリストに優先度キューを使用して、Nodeオブジェクトへのポインターを保存します。これはバイナリヒープとして実装され、浮動小数点値は常に正であり、プラットフォームの浮動小数点比較が遅いため、整数としてソートします。閉じたマップにハッシュテーブルを使用して、アクセスしたノードを追跡します。キャッシュサイズを節約するために、ノードではなくNodeIDを保存します。

できることをキャッシュする

初めてノードにアクセスして目的地までの距離を計算するときは、State Objectに保存されているノードにキャッシュします。ノードに再度アクセスする場合は、キャッシュされた結果を再度計算する代わりに使用してください。私の場合、再訪したノードで平方根を実行する必要はありません。事前に計算してキャッシュできる他の値がある場合があります。

さらに調査できる領域:双方向のパスファインディングを使用して、両端から検索します。私はこれをしていませんが、他の人がこれが役立つかもしれないと指摘したように、それは警告なしではありません。私が試してみるべきもう1つのことは、階層パス検索、またはクラスタリングパス検索です。HavokAIのドキュメントには興味深い説明があります。クラスタリングの概念については、ここで説明したHPA *実装とは異なります

幸運を祈ります。見つけたものをお知らせください。


ルールが異なり、多すぎない異なるエージェントが存在する場合、エージェントクラスごとに1つのIDのベクトルを使用することで、かなり効率的に一般化できます。
–MSalters

4
+1問題はおそらく通過不可能なタイル(通過できないタイルではない)であり、この種の問題は初期の読み込み時間の計算で簡単に解決できることを認識するため。
スリップD.トンプソン

塗りつぶしまたはBFS各エリア。
wolfdawn

アイランドIDは静的である必要はありません。2つの別々の島を結合する必要がある場合に適した単純なアルゴリズムがありますが、後で島を分割することはできません。これらのスライドの8〜20
〜bert

@kasperdはもちろん、実行時にマージされるアイランドIDの再計算を妨げるものはありません。ポイントは、アイランドIDを使用すると、astar検索を実行せずに2つのノード間にパスが存在するかどうかをすばやく確認できることです。
スティーブン

26

AStarは完全な計画アルゴリズムです。つまり、ノードへのパスが存在する場合、AStarはそれを見つけることが保証されます。そのため、目的のノードに到達できないと判断する前に、開始ノードからすべてのパスをチェックアウトする必要があります。ノードが多すぎる場合、これは非常に望ましくありません。

これを軽減する方法:

  • ノードが到達不能であることをアプリオリに知っている場合(例:隣接ノードがない、またはマークされているUnPassable)、No PathAStarを呼び出さずに戻ります。

  • AStarが終了する前に展開するノード数を制限します。オープンセットを確認します。大きくなりすぎた場合は、終了してを返しNo Pathます。ただし、これによりAStarの完全性が制限されます。そのため、最大長のパスのみを計画できます。

  • AStarがパスを見つけるのにかかる時間を制限します。時間切れになったら、終了してを返しNo Pathます。これにより、以前の戦略と同様に完全性が制限されますが、コンピューターの速度に応じて拡張されます。多くのゲームでは、これは望ましくないことに注意してください。これは、コンピューターの処理速度が速い場合も遅い場合も、ゲームのプレイ方法が異なるためです。


13
CPUの速度に応じてゲームの仕組みを変更する(そう、ルート検索はゲームの仕組みです)ことは、ゲームを非常に予測不能にしたり、場合によってはプレイ不能にしたりする可能性があるため、悪いアイデアになる可能性があることを指摘したいと思います10年後のコンピューターで。したがって、CPU時間よりもオープンセットに上限を設定してA *を制限することをお勧めします。
フィリップ

@フィリップ。これを反映するように回答を修正しました。
mklingen

1
特定のグラフについて、2つのノード間の最大距離を決定できることに注意してください(合理的に効率的なO(ノード))。これは最長パスの問題であり、チェックするノード数の正しい上限を提供します。
–MSalters

2
@MSalters O(n)でこれをどのように行いますか?そして、「合理的に効率的」とは何ですか?これがノードのペアのみの場合、作業を複製するだけではありませんか?
スティーブン

ウィキペディアによると、最長パスの問題は残念ながらNP困難です。
-Desty

21
  1. 同じループ内で同時にターゲットノードからデュアルA *検索を実行し、解決できないものが見つかったらすぐに両方の検索を中止します

ターゲットの周囲にアクセスできるタイルが6つのみで、オリジンにアクセス可能なタイルが1002ある場合、検索は6(デュアル)反復で停止します。

一方の検索が他方の訪問済みノードを見つけるとすぐに、検索範囲を他方の訪問済みノードに制限し、より速く終了することもできます。


2
双方向A-star検索の実装には、この状況下でヒューリスティックが許容されることを確認することを含め、ステートメントで暗示される以上のものがあります。(リンク:homepages.dcc.ufmg.br/~chaimo/public/ENIA11.pdf
Pieter Geerkens

4
@StephaneHockenhull:非対称コストで地形地図に双方向A- *を実現した、私は無視していることを保証学術何とか、何とかすると、障害のあるパスの選択と間違ったコスト計算になります。
ピータージャーケンズ

1
@MooingDuck:ノードの合計数は変更されず、各ノードはまだ1回しかアクセスされないため、マップを半分に正確に分割する最悪のケースは単方向A- *と同じです。
ピーターギアケンズ

1
@PieterGeerkens:従来のA *では、ノードの半分のみが到達可能であり、したがってアクセスされます。マップが正確に半分に分割されている場合、双方向に検索するときは、(ほぼ)すべてのノードをタッチします。ただし、間違いなくエッジケース
Mooing Duck

1
@MooingDuck:私は間違って話しました。最悪の場合は異なるグラフですが、同じ動作をします-単方向の最悪の場合は、完全に孤立した目標ノードであり、すべてのノードを訪問する必要があります。
ピータージャーケンズ

12

問題が宛先に到達できないと仮定します。また、ナビゲーションメッシュは動的ではありません。これを行う最も簡単な方法は、ナビゲーショングラフをよりスパースにし(フルランスルーが比較的速いほど十分にスパース)、パスが可能な場合にのみ詳細なグラフを使用することです。


6
これはいい。タイルを「領域」にグループ化し、最初にタイルが存在する領域が他のタイルが存在する領域に接続できるかどうかを確認することで、ネガをより速く捨てることができます。
コネラック

2
正解-一般的にHPA *に該当
スティーブン

@Stevenありがとう、私はそのようなアプローチを最初に考えた人ではないと確信していましたが、それが何と呼ばれているのか分かりませんでした。既存の研究をはるかに簡単に活用できます。
ClassicThunder

3

特性の異なる複数のアルゴリズムを使用する

A *にはいくつかの素晴らしい特徴があります。特に、存在する場合は常に最短パスを見つけます。残念ながら、あなたはいくつかの悪い特性も見つけました。この場合、ソリューションが存在しないことを認める前に、考えられるすべてのパスを徹底的に検索する必要があります。

A *で発見している「欠陥」は、トポロジーを認識していないことです。2次元の世界を持っているかもしれませんが、これはわかりません。知っている限りでは、あなたの世界の隅にある階段は、世界の真下を目的地に導く階段です。

トポロジを認識する2番目のアルゴリズムを作成することを検討してください。最初のパスとして、10または100スペースごとに「ノード」で世界を満たし、これらのノード間の接続性のグラフを維持します。このアルゴリズムは、開始点と終了点の近くでアクセス可能なノードを見つけてから、グラフ上のノード間のパスを探します(存在する場合)。

これを行う簡単な方法の1つは、各タイルをノードに割り当てることです。各タイルに1つのノードのみを割り当てる必要があることを示すのは簡単です(グラフで接続されていない2つのノードにアクセスすることはできません)。次に、グラフのエッジは、異なるノードを持つ2つのタイルが隣接する任意の場所に定義されます。

このグラフには欠点があります:最適なパスを見つけられません。それは単に見つけ、パスを。ただし、A *が最適なパスを見つけることができることが示されています。

また、A *関数を作成するために必要な過小評価を改善するためのヒューリスティックを提供します。これは、現在、ランドスケープについての詳細がわかっているためです。後退する必要があることを知る前に、行き止まりを完全に探索する必要はほとんどありません。


Googleマップのアルゴリズムのようなアルゴリズムは、同様の(より高度な)方法で動作すると信じる理由があります。
コートアンモン-モニカーの復活

違う。A *は、許容可能なヒューリスティックの選択により、トポロジを非常によく認識しています。
–MSalters

Googleについて、以前の仕事でGoogleマップのパフォーマンスを分析したところ、A *にはなり得なかったことがわかりました。ArcFlagsまたはマップの前処理に依存する他の同様のアルゴリズムを使用していると考えられます。
–MSalters

@MSalters:これは興味深い細かい線です。私は、A *がトポロジーに気付かないのは、それが最も近い隣人だけに関係していると主張するからです。許容できるヒューリスティックを生成するアルゴリズムは、A *自体ではなく、トポロジを認識していると言った方が公平だと思います。ダイヤモンドがある場合を考えてみましょう。A *は、ダイヤモンドの反対側を試すためにバックアップする前に、少しの間1つのパスを取ります。A *に、そのブランチからの唯一の「出口」がヒューリスティックで既にアクセスしたノード(計算の保存)を介していることを通知する方法はありません。
コートアンモン-モニカの復職

1
Googleマップについて話すことはできませんが、Bing Mapはランドマークと三角形の不等式(ALT)を備えた並列双方向A-starを使用し、少数のランドマークとすべてのノードから(および)事前に計算された距離を持ちます。
ピーターギアケンズ

2

上記の答えに加えて、さらにいくつかのアイデア:

  1. A *検索の結果をキャッシュします。セルAからセルBへのパスデータを保存し、可能であれば再利用します。これは静的マップでより適切であり、動的マップでより多くの作業を行う必要があります。

  2. 各セルの隣接セルをキャッシュします。A *の実装では、各ノードを展開し、その隣接ノードをオープンセットに追加して検索する必要があります。この近傍がキャッシュされるのではなく毎回計算される場合、検索が劇的に遅くなる可能性があります。また、すでに行っている場合は、A *に優先度キューを使用します。


1

マップが静的な場合は、A *を実行する前に、各セクションに独自のコードを用意し、最初にこれを確認するだけです。これは、マップの作成時に行うことも、マップにコーディングすることもできます。

通行不能なタイルにはフラグが必要で、そのようなタイルに移動するときは、A *を実行したり、その隣の到達可能なタイルを選択しないように選択できます。

頻繁に変更される動的なマップがある場合、ほとんど運がありません。完了前にアルゴリズムを停止するか、セクションのチェックを頻繁に行うかを決定する必要があります。


これはまさに、回答にエリアIDを使用して提案していたことです。
スティーブン

マップが動的であるが頻繁に変更されない場合は、使用されるCPU /時間の量を減らすこともできます。つまり、ロックされたドアがロック解除またはロックされるたびに、エリアIDを再計算できます。これは通常、プレイヤーのアクションに応じて発生するため、少なくともダンジョンのロックされた領域を除外する必要があります。
ウリウィットネス

1

ノードを通過できないとA *をより迅速に結論付けるにはどうすればよいですか?

Node.IsPassable()関数のプロファイルを作成し、最も遅い部分を見つけて、それらを高速化します。

ノードが通過可能かどうかを判断するときは、最も可能性の高い状況を一番上に置き、ほとんどの場合、関数があいまいな可能性を確認することなくすぐに戻るようにします。

ただし、これは単一ノードのチェックを高速化するためです。ノードの照会に費やされる時間を確認するためにプロファイルを作成できますが、チェックされているノードが多すぎることが問題のようです。

通行不能なタイルをクリックすると、アルゴリズムはどうやらマップ全体を通過して通行不能なタイルへのルートを見つけます

宛先タイル自体が通過できない場合、アルゴリズムはタイルをまったくチェックしません。パス検索を開始する前に、宛先タイルにクエリを実行して、可能かどうかを確認し、不可能な場合はパスなしの結果を返します。

目的地自体は通過可能であるが、パスがないように通過不可能なタイルで囲まれている場合、A *がマップ全体をチェックするのは正常です。他に経路がないことをどのように知るでしょうか?

後者の場合、双方向検索を実行することで速度を上げることができます。この方法では、宛先から開始する検索でパスがないことがすぐにわかり、検索を停止できます。この例を参照し、目的地を壁で囲み、双方向と単一方向を比較してください。


0

パス検索を逆方向に実行します。

マップに到達できないタイルの大きな連続領域がない場合のみ、これは機能します。パス検索では、到達可能なマップ全体を検索するのではなく、囲まれた到達不可能なエリアのみを検索します。


これは、到達可能なタイル上回っ到達不能タイルさらに遅い場合である
ダックMooing

1
@MooingDuck あなたが意味する接続された到達不能タイル。これは、ほぼすべての健全なマップ設計で機能するソリューションであり、実装は非常に簡単です。A *の実装が非常に遅いためにすべてのタイルを訪問することが実際に問題になるなど、正確な問題についての詳しい知識がなければ、手の込んだものを提案するつもりはありません。
aaaaaaaaaaaa

0

プレイヤーが接続されているエリア(テレポートなどがない)と到達不能なエリアが一般的にあまり接続されていない場合、到達したいノードから単純にA *を実行できます。そうすれば、目的地までのルートを引き続き見つけることができ、A *は到達不能エリアの検索をすぐに停止します。


ポイントは、通常のA *よりも高速であることでした。
ヘッケル

0

通行不能なタイルをクリックすると、アルゴリズムはどうやらマップ全体を通過して通行不可能なタイルへのルートを見つけます。

他の答えは素晴らしいですが、私は明白なことを指摘しなければなりません-パスファインディングを通過不可能なタイルに実行するべきではありません。

これは、アルゴリズムを早期に終了する必要があります。

if not IsPassable(A) or not IsPasable(B) then
    return('NoWayExists');

0

2つのノード間のグラフで最長距離を確認するには:

(すべてのエッジの重量が同じであると仮定)

  1. 任意の頂点からBFSを実行しますv
  2. 結果を使用して、最も遠い頂点を選択します。vこれを呼び出しますd
  3. からBFSを実行しますu
  4. から最も遠い頂点を見つけuますw。これを呼び出します。
  5. 間の距離uとは、wグラフ内の最長の距離です。

証明:

                D1                            D2
(v)---------------------------r_1-----------------------------(u)
                               |
                            R  | (note it might be that r1=r2)
                D3             |              D4
(x)---------------------------r_2-----------------------------(y)
  • 間の距離を言うことができますyし、x大きいです!
  • それからこれに従って D2 + R < D3
  • それから D2 < R + D3
  • その後、間の距離vとはxのそれよりも大きいvu
  • そして、u第1段階で選ばれなかったであろう。

教授の功績 シュロミ・ルビンスタイン

重み付きエッジを使用している場合、BFSの代わりにダイクストラを実行して最も遠い頂点を見つけることにより、多項式時間で同じことを実現できます。

接続グラフであると仮定していることに注意してください。私はそれが無向だと仮定しています。


A *は、単純な2Dタイルベースのゲームにはあまり役に立ちません。なぜなら、私が正しく理解すれば、クリーチャーが4方向に動くと仮定すると、BFSは同じ結果を達成するからです。クリーチャーが8方向に移動できる場合でも、ターゲットにより近いノードを好むレイジーBFSは同じ結果を達成します。A *は修正版のダイクストラであり、BFSを使用する場合よりもはるかに計算コストが高くなります。

BFS = O(| V |)おそらくO(| V | + | E |)ですが、実際にはトップダウンマップの場合はそうではありません。A * = O(| V | log | V |)

わずか32 x 32タイルのマップがある場合、BFSのコストは最大1024で、真のA *のコストはなんと10,000になります。これは0.5秒と5秒の差であり、キャッシュを考慮するとさらに大きくなる可能性があります。そのため、A *が、目的のターゲットに近いタイルを好むレイジーBFSのように動作することを確認してください。

A *は、意思決定プロセスでエッジのコストが重要であるナビゲーションマップに役立ちます。単純なオーバーヘッドタイルベースのゲームでは、エッジのコストはおそらく重要な考慮事項ではありません。イベント(異なるタイルのコストが異なる)の場合、キャラクターを遅くするタイルを通過するパスを延期し、ペナルティを科すBFSの修正バージョンを実行できます。

そのため、多くの場合、タイルに関してはBFS> A *です。


「32 x 32タイルのみのマップがある場合、BFSのコストは最大1024で、真のA *のコストはなんと10,000になります。」10kに至った経緯を説明してください番号ください?
Kromsterは、サポートモニカ

「ターゲットに近いノードを好む遅延BFS」とはどういう意味ですか?ダイクストラ、プレーンBFS、またはヒューリスティックなもの(ここでA *を再作成しましたか、またはオープンセットから次に最適なノードを選択する方法)を意味しますか?ことは、log|V|あなたの表記を使用してログ(| | V)はsqrt()について- A *の複雑さには本当にオープンセット、またはフリンジの大きさ、およびグリッドのためには、それは非常に小さいです写像することを維持することから来ています。ログ| V | ハイパー接続グラフにのみ表示されます。これは、最悪の場合の複雑さの単純なアプリケーションが誤った結論を与える例です。
コンガスボン

@congusbongusこれはまさに私の言いたいことです。A *のバニラ実装を使用しないでください
-wolfdawn

@KromSternタイルベースのゲームにA *のバニラ実装を使用すると仮定すると、V * logVの複雑さ、Vはタイルの数、32 x 32のグリッドでは1024になります。logV、おおよそビット数1024を表す必要があります。これは10です。したがって、不必要に長時間実行されることになります。もちろん、タイルのグリッドで実行しているという事実を活用するために実装を専門化すると、まさに私が言及していたこの制限を克服できます
-wolfdawn
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.