Einführung
Bei der Entwicklung meines Spiels habe ich die ersten NPCs erstellt. Und es stellte sich die Frage, wie man den NPC dazu bringt, um die Mauer herumzugehen und nicht "hineinzugehen".
Beim Klettern über das Internet habe ich folgende Algorithmen gefunden:
- Breite Suche (BFS, Breitensuche)
- Dijkstra-Algorithmus (Dijkstra)
- Ein Stern "A mit einem Stern"
- Suche nach der ersten besten Übereinstimmung (Best-First-Suche)
- IDA (A mit iterativer Vertiefung)
- Sprungpunktsuche
Und ich beschloss, mein A * in einem Voxel-3D-Raster zu implementieren.

Beispielkarte meines Spiels Beschreibung des Algorithmus
Ein * Schritt für Schritt durchsucht alle Pfade, die vom anfänglichen zum letzten Scheitelpunkt führen, bis der minimale gefunden wird. Wie alle informierten Suchalgorithmen betrachtet er zunächst die Routen, die zum Ziel führen. Der Greedy-Algorithmus, der auch der Suchalgorithmus für die erste beste Übereinstimmung ist, unterscheidet sich dadurch, dass bei der Auswahl eines Scheitelpunkts unter anderem der gesamte Weg berücksichtigt wird, der dorthin zurückgelegt wurde.
Visualisierung der Arbeit von A * auf Wikipedia Implementierung
Da der Algorithmus "Knoten" - Punkte zur Bestimmung des Pfades benötigt, schreiben wir die Klassenstruktur des Knotens:
Knotencode:public enum EMoveAction { walk, jump, fall, swim }; public class PathPoint {
Konstruktoren kleiner Klassen: private PathPoint NewPathPoint(Vector3 point, float pathLenghtFromStart, float heuristicEstimatePathLenght, EMoveAction moveAction) { PathPoint a = new PathPoint(); a.point = point; a.pathLenghtFromStart = pathLenghtFromStart; a.heuristicEstimatePathLenght = heuristicEstimatePathLenght; a.moveAction = moveAction; return a; } private PathPoint NewPathPoint(Vector3 point, float pathLenghtFromStart, float heuristicEstimatePathLenght, EMoveAction moveAction, PathPoint ppoint) { PathPoint a = new PathPoint(); a.point = point; a.pathLenghtFromStart = pathLenghtFromStart; a.heuristicEstimatePathLenght = heuristicEstimatePathLenght; a.moveAction = moveAction; a.cameFrom = ppoint; return a; }
Als nächstes wird die Struktur der Pfadsucheinstellungen nützlich sein:
Code für die Einstellungen für die Pfadsuche: public struct SPathFinderType {
Außerdem ist "World" eine Art Datenbankklasse zum Speichern von Informationen über Kartenblöcke. Ihre können anders implementiert werden.
Das Ergebnis der Pfadsuchfunktion zum Abrufen der Route: public List<PathPoint> GetPathToTarget(Vector3 beginPoint, Vector3 targetPoint, World worldData, SPathFinderType pfType) { List<PathPoint> path = new List<PathPoint>();
Nahpunkt
Die ClosePoint-Funktion hängt ausschließlich von der Implementierung der Weltklasse ab. Sie fügt der Liste der offenen Punkte alle möglichen Pfade hinzu und entfernt den aktuellen Punkt aus dieser Liste (schließt sie). Ich werde ein Beispiel für meinen "Schließpunkt" in den ersten vier Richtungen geben.
Warnung großer Codehaufen private List<PathPoint> ClosePoint(int index, List<PathPoint> openPoints, List<PathPoint> closedPoints, World worldData, SPathFinderType pfType, Vector3 targetPoint) { List<PathPoint> newOpenPoints = openPoints; PathPoint lastPoint = openPoints[index];
Optimierung
Indem wir den Pfad einfach vom Start bis zum aktuellen Punkt teilen, reduzieren wir die Anzahl der Knoten um ein Vielfaches und machen ihn gieriger.
return this.heuristicEstimatePathLenght + this.pathLenghtFromStart /2;
Zusammenfassung
Vorteile:
- Schnelle Suche in offenen Räumen.
- Vielseitigkeit des Algorithmus
Nachteile:
- Die Berechnung der Route erfordert viel Speicher.
Grün zeigt eine offene Liste von Knoten, roten Pfad zum Ziel, blau geschlossene Knoten.
Erhaltene Routen vor der Optimierung: Erhaltene Routen nach der Optimierung: Literatur
https://tproger.ru/articles/pathfindings/
https://ru.wikipedia.org/wiki/A*