エンティティがダイムをオンにできない車のような動きを想像してみてください。話し合いのために、速度が上がると毎秒90度回転できるとしましょう。これにより、多くの場合、最適パスが変更され、したがってパスファインディングが変更されます。「通常の」パスを通過することがまったく不可能になることさえあります。
これを覚えておくことができるパスファインディングアルゴリズムや移動計画アルゴリズムはありますか、または人気のアルゴリズムを適応させる簡単な方法はありますか?
エンティティがダイムをオンにできない車のような動きを想像してみてください。話し合いのために、速度が上がると毎秒90度回転できるとしましょう。これにより、多くの場合、最適パスが変更され、したがってパスファインディングが変更されます。「通常の」パスを通過することがまったく不可能になることさえあります。
これを覚えておくことができるパスファインディングアルゴリズムや移動計画アルゴリズムはありますか、または人気のアルゴリズムを適応させる簡単な方法はありますか?
回答:
非ホロノミック運動計画の素晴らしい世界へようこそ。ラティスグリッドパスプランナーを使用することをお勧めします。他の代替として、kinodynamic RRT、および軌道の最適化を。非ホロノミックシステムには、車、ボート、一輪車など、車両が目的の方向に移動できないあらゆるものが含まれます。これらのシステムの計画はホロノミックシステムよりもはるかに困難であり、2000年までは学術研究の最前線でした。最近では、適切に機能するアルゴリズムから選択する多くのアルゴリズムがあります。
これがどのように機能するかです。
状態
車の構成qは、実際には車のx、y位置とその向きtを含む3D状態です。A *アルゴリズムのノードは、実際には3Dベクトルです。
class Node
{
// The position and orientation of the car.
float x, y, theta;
}
行動
それで、エッジはどうですか?
あなたの車は実際に無限の数の方法でハンドルを回すことができるので、それは少し難しいです。したがって、車が実行できるアクションの数を離散セットAに制限することで、これをラティスグリッドプランナーにアクセス可能にすることができます。簡単にするために、車は加速せず、速度を瞬時に変更できると仮定します。この場合、Aは次のようになります。
class Action
{
// The direction of the steering wheel.
float wheelDirection;
// The speed to go at in m/s.
float speed;
// The time that it takes to complete an action in seconds.
float dt;
}
これで、車がいつでも実行できる個別のアクションセットを作成できます。たとえば、ガスを0.5秒間完全に押しているときのハードライトは次のようになります。
Action turnRight;
turnRight.speed = 1;
turnRight.wheelDirection = 1;
turnRight.dt = 0.5;
車をリバースに入れてバックアップすると、次のようになります。
Action reverse;
reverse.speed = -1;
reverse.wheelDirection = 0;
reverse.dt = 0.5;
そして、アクションのリストは次のようになります。
List<Action> actions = { turnRight, turnLeft, goStraight, reverse ...}
また、ノードで実行されたアクションが新しいノードになる方法を定義する方法も必要です。これは、システムのフォワードダイナミクスと呼ばれます。
// These forward dynamics are for a dubin's car that can change its
// course instantaneously.
Node forwardIntegrate(Node start, Action action)
{
// the speed of the car in theta, x and y.
float thetaDot = action.wheelDirection * TURNING_RADIUS;
// the discrete timestep in seconds that we integrate at.
float timestep = 0.001;
float x = start.x;
float y = start.y;
float theta = start.theta;
// Discrete Euler integration over the length of the action.
for (float t = 0; t < action.dt; t += timestep)
{
theta += timestep * thetaDot;
float xDot = action.speed * cos(theta);
float yDot = action.speed * sin(theta);
x += timestep * xDot;
y += timestep * yDot;
}
return Node(x, y, theta);
}
離散グリッドセル
ここで、ラティスグリッドを構築するために必要なのは、自動車の状態を個別のグリッドセルにハッシュすることだけです。これにより、A *が後に続く個別のノードに変わります。それ以外の場合、A *は2つの車の状態を比較するために実際に同じかどうかを知る方法がないため、これは非常に重要です。整数のグリッドセル値にハッシュすることで、これは簡単になります。
GridCell hashNode(Node node)
{
GridCell cell;
cell.x = round(node.x / X_RESOLUTION);
cell.y = round(node.y / Y_RESOLUTION);
cell.theta = round(node.theta / THETA_RESOLUTION);
return cell;
}
これで、GridCellがノードであり、アクションがノード間のエッジであり、開始と目標がGridCellで表現されるA *プランを実行できます。2つのGridCell間のヒューリスティックは、xとyの距離にシータの角距離を加えたものです。
パスをたどる
GridCellsとActionsの間のパスができたので、車のパスフォロワーを作成できます。グリッドセルは離散しているため、車はセル間をジャンプします。したがって、パスに沿った車の動きを滑らかにする必要があります。ゲームが物理エンジンを使用している場合、これは、車をできるだけパスに近づけようとするステアリングコントローラーを作成することで実現できます。それ以外の場合は、ベジェカーブを使用するか、パス内の最も近い数点を平均するだけで、パスをアニメーション化できます。
ほとんどの経路探索アルゴリズムは、ジオメトリの制限なしに任意のグラフで機能します。
したがって、必要なのは、探索された各ノードに車の向きを追加し、実際に接続されているノードを制限することです。
私の考え、それらをテストしていない!
また、最初にパスを完了しなくても、これを実行できるはずです:ergo:A *の間にターンを処理します。これは、おそらくはるかに最適化されますが、問題がありグリッチであることが判明する可能性もあります。自分でテストする時間はありません。
エージェントが車を完全に制御できる場合は、逆の方法で行います。最初から最初から最後まで線を接続し、次にデニスの答えと同様に、各ターンをナビゲートできる速度を把握します。
ただし、固定点からベジェ曲線を描画しないでください。速度の低下を最小限に抑えるには、ライン全体を移動する必要があるため、まず等間隔で追加のノードを挿入し、次にエネルギーの最小化または同様の戦略のために移動します。詳細については、(できればシムまたはセミシムの)レーシングゲームでAIライン生成を調べる必要があります。
AIラインシステムを実行したら、A *検索を実行し、各パスについて少なくとも1コーナー前に進み、時間の見積もりを与えるAIラインを計算します。これがコスト関数になります。