編集:わかりました、コメントの1つから自動ナビゲーションを望まないことを読みました。その場合、リストがマウスをクリックした場所のみを保存し、障害物が見つかったときにそれ自体をクリアするという単純なケースで、この投稿の最後のセクション(「そのパスを歩く」)のみを考慮してください。また、投稿で一度タイルについて言及していることにも気づきました。その場合、可視性グラフは必要ありません。グリッド自体を使用して、Aスターを実行できます。とにかく、私はまだこれを2Dポイントアンドクリックナビゲーションの問題に対するより一般的な解決策として投稿しています。
あなたが求めているのは、2D環境でパスファインディングを行う方法です。その問題を解決できる1つのテクニックの概要を説明する前に、記事を書きました。まず、記事へのリンクを配置してから、アルゴリズムの簡単な説明を追加します。
http://www.david-gouveia.com/pathfinding-on-a-2d-polygonal-map/
もちろん、これがそれを解決する唯一の方法ではありません。可視性グラフを使用しています。ナビゲーションメッシュを使用することもできます。またはグリッド。可視性グラフには、パスの直線化を行わなくても、常にポイント間の最も直接的なパスが返されるという1つの利点があります。ポリゴンの上に可視性グラフを作成すると、歩行可能な領域を正確に指定できます。
概念
ここでの主なアイデアは、歩行可能な領域をポリゴンとして表し、ポリゴンの凹状の頂点を使用して可視性グラフを作成することです。ポリゴンに穴が含まれている場合は、凸状の頂点も使用します。
可視性グラフを作成するということは、グラフのすべてのノード(この場合は頂点)を取得して、「見える」他のすべての頂点に接続することを意味します。これを行うには、視線チェックが必要です。私が使用したものは、いくつかのチェックが追加された、単純な線分交差テストの上に構築されています。
次に、2つの場所の間のパスを見つけたいときはいつでも、それらを可視性グラフに一時的に追加し、その上で従来のA *パスファインディングアルゴリズムを実行します。
全体の構造は次のようになります。
どこ:
- 黄色い線は、どこを歩くことができるかを表すポルジョンです。
- 白い円は、可視性グラフ(ノード)を構成するポリゴンの頂点です。
- 紫色の線は、互いの見通し内にある頂点(エッジ)を接続します。
- 水色の線は、2つの場所(緑の点と赤の点)の間のパスを見つける例です。
- 明るい緑色の線は、パスの開始ノードと終了ノード(緑色の点と赤色の点)とともにグラフに追加される一時的なエッジです。
実装
1)表現
これを実装するには、まず、床のポリゴンを表す方法が必要です。次のクラスで十分です:
public class Polygon
{
public class SimplePolygon
{
List<Vector2> Vertices;
}
List<SimplePolygon> Outlines;
List<SimplePolygon> Holes;
}
2)ノードの選択
次に、ポリゴンの各頂点を調べて、それらを可視性グラフのノードにするかどうかを決定する方法が必要です。その基準は、輪郭の凹状の頂点と穴の凸状の頂点です。私はこのような関数を使います:
public static bool IsVertexConcave(IList<Vector2> vertices, int vertex)
{
Vector2 current = vertices[vertex];
Vector2 next = vertices[(vertex + 1) % vertices.Count];
Vector2 previous = vertices[vertex == 0 ? vertices.Count - 1 : vertex - 1];
Vector2 left = new Vector2(current.X - previous.X, current.Y - previous.Y);
Vector2 right = new Vector2(next.X - current.X, next.Y - current.Y);
float cross = (left.X * right.Y) - (left.Y * right.X);
return cross < 0;
}
3)エッジの選択
次に、これらの頂点のすべてのペアを調べ、それらが見通し内にあるかどうかを判断する必要があります。そのチェックの開始点として次の方法を使用しました。
static bool LineSegmentsCross(Vector2 a, Vector2 b, Vector2 c, Vector2 d)
{
float denominator = ((b.X - a.X) * (d.Y - c.Y)) - ((b.Y - a.Y) * (d.X - c.X));
if (denominator == 0)
{
return false;
}
float numerator1 = ((a.Y - c.Y) * (d.X - c.X)) - ((a.X - c.X) * (d.Y - c.Y));
float numerator2 = ((a.Y - c.Y) * (b.X - a.X)) - ((a.X - c.X) * (b.Y - a.Y));
if (numerator1 == 0 || numerator2 == 0)
{
return false;
}
float r = numerator1 / denominator;
float s = numerator2 / denominator;
return (r > 0 && r < 1) && (s > 0 && s < 1);
}
しかし、エッジケースの安定性のために他のいくつかのハックを使用する必要があったので、投稿するのは良い状態ではありません。まだ問題のクリーンで安定した解決策を見つけようとしています。
4)グラフを作成し、Aスターを実行する
これらの頂点とエッジを使用して可視性グラフを作成し、A *を実行する必要があります。グラフを作成してA *を適用する方法を学ぶには、次の記事を読むことをお勧めします。
http://blogs.msdn.com/b/ericlippert/archive/2007/10/02/path-finding-using-a-in-c-3-0.aspx
次に、これらすべてを単一のクラスにカプセル化して、次のような使いやすいインターフェイスを用意することができます。
public class Floor
{
public Floor(Polygon polygon)
{
_polygon = polygon;
BuildGraph();
}
public IEnumerable<Vector> GetPath(Vector2 start, Vector2 end)
{
// Add start and end as temporary nodes and connect them to the graph
// Run A* on the graph
// Remove temporary nodes and edges
}
private Polygon _polygon;
private Graph _graph;
}
この方法では、Floorインスタンスを作成し、2つの場所の間のパスを見つける必要があるときはいつでも、そのインスタンスでGetPathメソッドを呼び出す必要があります。
5)その道を歩く
最後に、キャラクターに生成されたパスを歩くようにする必要があります。そのためには何らかの内部メモリが必要ですが、実装するのはそれほど難しくありません。例えば:
- キャラクター内にリストを追加して、彼が現在フォローしているパスを保存します。
- 各更新サイクルで、リストから最初の値を取得し、それに向かってキャラクターを移動します。
- 彼が目的地に十分近づいたら、リストから最初の要素を削除して繰り返します。
- リストが空になると、目的地に到着したことになります。