Bearbeiten: Okay, ich habe gerade aus einem Ihrer Kommentare gelesen, dass Sie keine automatische Navigation wünschen. Betrachten Sie in diesem Fall nur den letzten Abschnitt in diesem Beitrag ("Walking that Path") mit dem einfachen Fall, dass die Liste nur die Stelle speichert, an der Ihre Maus geklickt hat, und sich selbst löscht, wenn ein Hindernis im Weg ist. Mir ist auch gerade aufgefallen, dass Sie Kacheln einmal in der Post erwähnt haben. In diesem Fall ist kein Sichtbarkeitsdiagramm erforderlich. Das Raster selbst kann zum Ausführen von A-Star verwendet werden. Wie auch immer, ich poste dies immer noch als allgemeinere Lösung für das 2D-Point-and-Click-Navigationsproblem.
Was Sie fragen, ist, wie Sie die Pfadfindung in einer 2D-Umgebung durchführen. Ich habe einen Artikel geschrieben, bevor ich eine Technik skizziere, mit der dieses Problem gelöst werden kann. Ich werde zunächst einen Link zum Artikel erstellen und dann eine kurze Erläuterung des Algorithmus hinzufügen.
http://www.david-gouveia.com/pathfinding-on-a-2d-polygonal-map/
Dies ist natürlich nicht der einzige Weg, dies zu lösen. Ich verwende ein Sichtbarkeitsdiagramm. Sie können auch ein Navigationsnetz verwenden. Oder ein Gitter. Das Sichtbarkeitsdiagramm hat den besonderen Vorteil, dass es immer den direktesten Pfad zwischen Punkten zurückgibt, ohne dass eine Pfadkorrektur erforderlich ist. Wenn Sie das Sichtbarkeitsdiagramm über einem Polygon erstellen, können Sie die begehbaren Bereiche genau angeben.
Konzept
Die Hauptidee hier ist, Ihren begehbaren Bereich als Polygon darzustellen und ein Sichtbarkeitsdiagramm unter Verwendung der konkaven Eckpunkte des Polygons zu erstellen. Wenn das Polygon Löcher enthält, verwenden Sie auch deren konvexe Eckpunkte.
Um ein Sichtbarkeitsdiagramm zu erstellen, müssen Sie jeden Knoten (oder in diesem Fall den Scheitelpunkt) des Diagramms mit jedem anderen Scheitelpunkt verbinden, den es "sehen" kann. Dazu benötigen Sie eine Sichtlinienprüfung. Der von mir verwendete basiert auf einem einfachen Liniensegment-Schnittpunkttest mit einigen zusätzlichen Überprüfungen.
Wenn Sie dann den Pfad zwischen zwei Positionen suchen möchten, fügen Sie diese vorübergehend zum Sichtbarkeitsdiagramm hinzu und führen einfach einen klassischen A * -Pfadfindungsalgorithmus darauf aus.
So sieht die gesamte Struktur aus:
Wo:
- Die gelben Linien sind das Polgyon, das angibt, wo Sie gehen können.
- Die weißen Kreise sind die Polygonscheitelpunkte, aus denen das Sichtbarkeitsdiagramm (Knoten) besteht.
- Die violetten Linien verbinden Scheitelpunkte, die sich in Sichtweite zueinander befinden (Kanten).
- Die hellblaue Linie ist ein Beispiel für die Suche nach einem Pfad zwischen zwei Orten (grüner Punkt und roter Punkt).
- Die hellgrünen Linien sind temporäre Kanten, die dem Diagramm zusammen mit den Start- und Endknoten im Pfad (grüner Punkt und roter Punkt) hinzugefügt werden.
Implementierung
1) Vertretung
Um dies zu implementieren, müssen Sie zunächst eine Möglichkeit haben, ein Polygon für Ihren Boden darzustellen. Die folgende Klasse sollte ausreichen:
public class Polygon
{
public class SimplePolygon
{
List<Vector2> Vertices;
}
List<SimplePolygon> Outlines;
List<SimplePolygon> Holes;
}
2) Knoten auswählen
Dann müssen Sie einen Weg finden, um die Scheitelpunkte des Polygons zu durchlaufen und zu entscheiden, ob sie Knoten im Sichtbarkeitsdiagramm sein sollen. Das Kriterium dafür sind konkave Eckpunkte im Umriss und konvexe Eckpunkte in den Löchern. Ich benutze eine Funktion wie diese:
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) Kanten auswählen
Jetzt müssen Sie jedes Paar dieser Scheitelpunkte durchgehen und entscheiden, ob sie in Sichtweite sind oder nicht. Ich habe die folgende Methode als Ausgangspunkt für diese Prüfung verwendet:
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);
}
Musste aber auf ein paar andere Hacks zurückgreifen, um die Stabilität von Randfällen zu gewährleisten, so dass das Posten nicht in gutem Zustand ist. Ich versuche immer noch, eine saubere und stabile Lösung für das Problem zu finden.
4) Konstruieren Sie den Graphen und führen Sie einen A-Stern aus
Sie müssen ein Sichtbarkeitsdiagramm mit diesen Scheitelpunkten und Kanten erstellen und A * darauf ausführen. Um zu lernen, wie man ein Diagramm erstellt und A * anwendet, empfehle ich, den folgenden Artikel zu lesen:
http://blogs.msdn.com/b/ericlippert/archive/2007/10/02/path-finding-using-a-in-c-3-0.aspx
Möglicherweise möchten Sie all dies in einer einzigen Klasse zusammenfassen, damit Sie eine einfache Benutzeroberfläche verwenden können, z. B.:
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;
}
Auf diese Weise müssen Sie nur eine Floor-Instanz erstellen und die GetPath-Methode darauf aufrufen, wenn Sie den Pfad zwischen zwei Speicherorten suchen müssen.
5) Diesen Weg gehen
Schließlich müssen Sie Ihren Charakter dazu bringen, den generierten Pfad zu gehen. Dafür braucht er einen internen Speicher, aber es ist nicht allzu schwer, das umzusetzen. Zum Beispiel:
- Fügen Sie dem Charakter eine Liste hinzu, um den Pfad zu speichern, dem er gerade folgt.
- Nehmen Sie bei jedem Aktualisierungszyklus den ersten Wert aus der Liste und bewegen Sie Ihren Charakter darauf zu.
- Wenn er nahe genug am Ziel ist, entfernen Sie das erste Element aus der Liste und wiederholen Sie den Vorgang.
- Wenn die Liste leer wird, hat er sein Ziel erreicht.