A*-Algorithmus [Edit 2]

  • C#

Es gibt 27 Antworten in diesem Thema. Der letzte Beitrag () ist von Eistee.

    A*-Algorithmus [Edit 2]

    Hallo,

    so nach Tagen der fummelei funktioniert es noch immer nicht wie es soll, daher wollte ich mal fragen ob ihr etwas fertiges kennt, was zugleich leicht zu implementieren ist.
    Hätte es gerne selbst geschafft, aber ich kann die Fehler in meinem Code nicht finden. Der Weg selbst wird gefunden es ist aber nicht der kürzeste.
    Das mit den diagonalen neben den Mauern könnte ich fixen aber dann wäre die Wegfindung ja noch immer nicht richtig. Hier mal ein Bild:


    *Topic verschoben*

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „Eistee“ ()

    Das Video hilft leider nicht wirklich, denn wenn ich die Diagonalen Felder rausnehme (wie im Video) funktioniert es wie es soll.
    Nur Wenn ich meine Objekte nach diesem Weg bewege, sieht das ganze sehr unnatürlich aus.
    Also eine "gerade"-Diagoinale würde in Stufen abgefahren/abgelaufen (Wie bei Snake), was bei einem Auto total bescheuert aussieht ^^

    Ohne Diagonale wie im Video:
    (Der Startpunkt ist nicht da wo das Modell steht, sondern beim 1. rosa Kasten daneben)
    @nafets3646: Weil ich eine absolute Null in Mathe bin und absolut keine Ahnung habe wie man das macht.
    Ich kann mir zwar vorstellen das man sich 2 Punkte sucht (wie die nach der Mauer auf dem Bild), wo man dann
    eine Diagonale ziehen kann (also alle Felder 'frei' sind), aber wie man soetwas macht ?(

    Oder in der Form:

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Eistee“ ()

    @Eistee
    zeig doch mal deinen kompletten Code^^ Ansonsten setzt doch einfach den Pseudocode von z.B. Wikipedia um

    @nafets3646
    Die Strecke zu smoothen dürfte nicht funktionieren, wenn du iwelche Schrägen einbaust, dann könnten dort ja Kollisionen entstehen.
    Es macht eigentlich keinen Unterschied. Ich persönlich wäre sogar glücklicher, wenn du C# postest, weil ich das besser mag aber auch, weil die Converter schlecht sind. So oder so wirst du wohl genau gleich kompetente Hilfe erhalten. Wenn nen Moderator so pingelig ist, dann soll ers halt verschieben, macht ja nichts. ;)
    Ich Editiere in wenigen Minuten beides (C# & VB) hier drunter.
    Aber ob ihr den Mist nachvollziehen könnt ist eine andere Geschichte ^^

    Vb
    Knoten

    VB.NET-Quellcode

    1. Class Knoten
    2. Public Property position() As Vector2
    3. Get
    4. Return m_position
    5. End Get
    6. Set
    7. m_position = Value
    8. End Set
    9. End Property
    10. Private m_position As Vector2
    11. Public Property vorgaenger() As Knoten
    12. Get
    13. Return m_vorgaenger
    14. End Get
    15. Set
    16. m_vorgaenger = Value
    17. End Set
    18. End Property
    19. Private m_vorgaenger As Knoten
    20. Public Property H_geschaetzteKosten() As Integer
    21. Get
    22. Return m_H_geschaetzteKosten
    23. End Get
    24. Set
    25. m_H_geschaetzteKosten = Value
    26. End Set
    27. End Property
    28. Private m_H_geschaetzteKosten As Integer
    29. Public Property G_GesamtFeldkosten() As Integer
    30. Get
    31. Return m_G_GesamtFeldkosten
    32. End Get
    33. Set
    34. m_G_GesamtFeldkosten = Value
    35. End Set
    36. End Property
    37. Private m_G_GesamtFeldkosten As Integer
    38. Public Property F_StartZumZielUeberHierKosten() As Integer
    39. Get
    40. Return m_F_StartZumZielUeberHierKosten
    41. End Get
    42. Set
    43. m_F_StartZumZielUeberHierKosten = Value
    44. End Set
    45. End Property
    46. Private m_F_StartZumZielUeberHierKosten As Integer
    47. Private Const geradeKosten As Integer = 10
    48. Private Const diagonalKosten As Integer = 14
    49. Public Sub New(Position__1 As Vector2)
    50. position = Position__1
    51. End Sub
    52. Public Function isInList(Liste As List(Of Knoten)) As [Boolean]
    53. For x As Integer = 0 To Liste.Count - 2
    54. If Liste(x).position = Me.position Then
    55. Return True
    56. End If
    57. Next
    58. Return False
    59. End Function
    60. Public Function getIndexBasedOnPosition(Liste As List(Of Knoten)) As Integer
    61. For x As Integer = 0 To Liste.Count - 2
    62. If Liste(x).position = Me.position Then
    63. Return x
    64. End If
    65. Next
    66. Return -1
    67. End Function
    68. Public Function getDirectionalCostBasedOnPredecessor() As Integer
    69. If Me.vorgaenger IsNot Nothing Then
    70. If Me.vorgaenger.position.X = Me.position.X OrElse Me.vorgaenger.position.Y = Me.position.Y Then
    71. ' Der Knoten ist Wagerecht oder Senkrecht zum Knoten
    72. Return 10
    73. Else
    74. ' Der Knoten ist Diagonal zum Knoten
    75. Return 14
    76. End If
    77. Else
    78. Throw New ArgumentNullException("Knoten.vorgaenger", "Dieser Knoten hat keinen Vorgänger")
    79. End If
    80. End Function
    81. Public Function getDirectionalCostOver(Knoten As Knoten) As Integer
    82. If Knoten.position.X = Me.position.X OrElse Knoten.position.Y = Me.position.Y Then
    83. ' Der Knoten ist Wagerecht oder Senkrecht zum Knoten
    84. Return 10
    85. Else
    86. ' Der Knoten ist Diagonal zum Knoten
    87. Return 14
    88. End If
    89. End Function
    90. Public Function getNeighbors(karte As Map) As List(Of Point)
    91. Dim list As New List(Of Point)()
    92. Dim x As Integer = 0, y As Integer = 0
    93. For i As Integer = Math.Max(x - 1, 0) To Math.Min(x + 2, karte.Tiles.GetUpperBound(0)) - 1
    94. For j As Integer = Math.Max(y - 1, 0) To Math.Min(y + 2, karte.Tiles.GetUpperBound(1)) - 1
    95. If i = x AndAlso j = y Then
    96. Continue For
    97. End If
    98. list.Add(New Point(x, y))
    99. Next
    100. Next
    101. Return list
    102. End Function
    103. Public Function getWalkableNeighbors(karte As Map) As List(Of Point)
    104. Dim list As New List(Of Point)()
    105. Dim x As Integer = CInt(Me.position.X), y As Integer = CInt(Me.position.Y)
    106. For i As Integer = Math.Max(x - 1, 0) To Math.Min(x + 2, karte.Tiles.GetUpperBound(0)) - 1
    107. For j As Integer = Math.Max(y - 1, 0) To Math.Min(y + 2, karte.Tiles.GetUpperBound(1)) - 1
    108. If i = x AndAlso j = y Then
    109. Continue For
    110. ElseIf karte.Tiles(i, j).begehbar Then
    111. list.Add(New Point(i, j))
    112. End If
    113. Next
    114. Next
    115. Return list
    116. End Function
    117. End Class

    Objekt mit der Funktion einen Weg zu finden

    VB.NET-Quellcode

    1. Private Function findPathAStar(start As Vector2, ziel As Vector2) As List(Of Knoten)
    2. ' reset
    3. openList.Clear()
    4. closedList.Clear()
    5. Dim zielKnoten As New Knoten(ziel)
    6. ' end
    7. ' Beginne mit Startpunkt A und füge ihn einer "offenen" Liste von Quadraten hinzu
    8. Dim startKnoten As New Knoten(New Vector2(start.X, start.Y))
    9. openList.Add(startKnoten)
    10. ' Füge sie der o.g. Liste hinzu und merke für jedes dieser Quadrate das Startquadrat A als seinen Vorgänger
    11. For Each p As Point In startKnoten.getWalkableNeighbors(karte)
    12. Dim ktn As New Knoten(New Vector2(p.X, p.Y))
    13. ktn.vorgaenger = startKnoten
    14. openList.Add(ktn)
    15. Next
    16. ' Wirf das Startquadrat A aus der offenen Liste und füge es der geschlossenen Liste hinzu
    17. openList.removeWithPosition(startKnoten.position)
    18. closedList.Add(startKnoten)
    19. ' Berechne alle Kosten aller Quadrate in der offenen Liste
    20. For Each knt As Knoten In openList
    21. If knt.vorgaenger IsNot Nothing Then
    22. knt.G_GesamtFeldkosten = knt.vorgaenger.G_GesamtFeldkosten + knt.getDirectionalCostBasedOnPredecessor()
    23. Else
    24. knt.G_GesamtFeldkosten = 0
    25. End If
    26. knt.H_geschaetzteKosten = Math.Abs(CInt(((ziel.X - start.X) + (ziel.Y - start.Y)) * 10))
    27. knt.F_StartZumZielUeberHierKosten = knt.G_GesamtFeldkosten + knt.H_geschaetzteKosten
    28. Next
    29. While Not zielKnoten.isInList(closedList)
    30. ' Das Quadrat mit den niedrigsten F-Kosten.
    31. Dim aktuell As Knoten = openList.getLowestF()
    32. ' Entferne es aus der offenen Liste
    33. openList.removeWithPosition(aktuell.position)
    34. ' füge es der geschlossenen Liste hinzu
    35. closedList.Add(aktuell)
    36. ' Prüfe alle angrenzenden Quadrate und füge sie der offenen Liste hinzu (WENN: begehbar, nicht schon in offener/geschlossener Liste)
    37. For Each p As Point In aktuell.getWalkableNeighbors(karte)
    38. Dim ktn As New Knoten(New Vector2(p.X, p.Y))
    39. ktn.vorgaenger = aktuell
    40. ' Wenn nicht in geschlossener Liste
    41. If Not ktn.isInList(closedList) Then
    42. ' Wenn nicht in offener Liste
    43. If Not ktn.isInList(openList) Then
    44. openList.Add(ktn)
    45. Else
    46. ' Wenn in offener Liste
    47. ' WENN G-Wert für dieses Quadrat (ktn) wenn wir vom aktuellen dort hingehen [z. B. Recht und dann Hoch]
    48. Dim alterGWert As Integer = ktn.G_GesamtFeldkosten
    49. Dim neueGWert As Integer = aktuell.G_GesamtFeldkosten + aktuell.getDirectionalCostOver(ktn)
    50. ' Weg über (aktuell) wäre kürzer:
    51. If neueGWert < alterGWert Then
    52. ' Wir ändern den Vorgänger von ktn auf das aktuelle Quadrat
    53. ktn.vorgaenger = aktuell
    54. ' Berechnen die G-Kosten mit dem neuen Vorgänger sowie die neuen F-Kosten
    55. ktn.G_GesamtFeldkosten = ktn.vorgaenger.G_GesamtFeldkosten + ktn.getDirectionalCostBasedOnPredecessor()
    56. ktn.F_StartZumZielUeberHierKosten = ktn.G_GesamtFeldkosten + ktn.H_geschaetzteKosten
    57. End If
    58. End If
    59. End If
    60. Next
    61. End While
    62. Dim _path As New List(Of Knoten)()
    63. Dim indexOfZiel As Integer = zielKnoten.getIndexBasedOnPosition(closedList)
    64. Dim tempKnoten As Knoten = closedList(indexOfZiel)
    65. While tempKnoten.vorgaenger IsNot Nothing
    66. _path.Add(tempKnoten)
    67. tempKnoten = tempKnoten.vorgaenger
    68. End While
    69. Return _path
    70. End Function

    Extensions

    VB.NET-Quellcode

    1. NotInheritable Class MyExtensions
    2. Private Sub New()
    3. End Sub
    4. <System.Runtime.CompilerServices.Extension> _
    5. Public Shared Function getLowestF(list As List(Of Knoten)) As Knoten
    6. Dim kMitMinFKosten As Knoten = list(0)
    7. For x As Integer = 0 To list.Count - 2
    8. If kMitMinFKosten.F_StartZumZielUeberHierKosten > list(x).F_StartZumZielUeberHierKosten Then
    9. kMitMinFKosten = list(x)
    10. End If
    11. Next
    12. Return kMitMinFKosten
    13. End Function
    14. <System.Runtime.CompilerServices.Extension> _
    15. Public Shared Sub removeWithPosition(list As List(Of Knoten), position As Vector2)
    16. For x As Integer = 0 To list.Count - 2
    17. If list(x).position = position Then
    18. list.RemoveAt(x)
    19. End If
    20. Next
    21. End Sub
    22. End Class


    C#
    Knoten

    C#-Quellcode

    1. class Knoten
    2. {
    3. public Vector2 position {get; set; }
    4. public Knoten vorgaenger { get; set; }
    5. public int H_geschaetzteKosten { get; set; }
    6. public int G_GesamtFeldkosten { get; set; }
    7. public int F_StartZumZielUeberHierKosten { get; set; }
    8. private const int geradeKosten = 10;
    9. private const int diagonalKosten = 14;
    10. public Knoten(Vector2 Position)
    11. {
    12. position = Position;
    13. }
    14. public Boolean isInList(List<Knoten> Liste){
    15. for (int x = 0; x < Liste.Count - 1; x++){
    16. if (Liste[x].position == this.position){
    17. return true;
    18. }
    19. }
    20. return false;
    21. }
    22. public int getIndexBasedOnPosition(List<Knoten> Liste)
    23. {
    24. for (int x = 0; x < Liste.Count - 1; x++)
    25. {
    26. if (Liste[x].position == this.position)
    27. {
    28. return x;
    29. }
    30. }
    31. return -1;
    32. }
    33. public int getDirectionalCostBasedOnPredecessor(){
    34. if (this.vorgaenger != null)
    35. {
    36. if (this.vorgaenger.position.X == this.position.X ||
    37. this.vorgaenger.position.Y == this.position.Y){
    38. return 10; // Der Knoten ist Wagerecht oder Senkrecht zum Knoten
    39. }
    40. else
    41. {
    42. return 14; // Der Knoten ist Diagonal zum Knoten
    43. }
    44. }
    45. else
    46. {
    47. throw new ArgumentNullException("Knoten.vorgaenger", "Dieser Knoten hat keinen Vorgänger");
    48. }
    49. }
    50. public int getDirectionalCostOver(Knoten Knoten)
    51. {
    52. if (Knoten.position.X == this.position.X ||
    53. Knoten.position.Y == this.position.Y)
    54. {
    55. return 10; // Der Knoten ist Wagerecht oder Senkrecht zum Knoten
    56. }
    57. else
    58. {
    59. return 14; // Der Knoten ist Diagonal zum Knoten
    60. }
    61. }
    62. public List<Point> getNeighbors(Map karte)
    63. {
    64. List<Point> list = new List<Point>();
    65. int x=0, y = 0;
    66. for (int i = Math.Max(x - 1, 0); i < Math.Min(x + 2, karte.Tiles.GetUpperBound(0)); i++)
    67. {
    68. for (int j = Math.Max(y - 1, 0); j < Math.Min(y + 2, karte.Tiles.GetUpperBound(1)); j++)
    69. {
    70. if (i == x && j == y)
    71. continue;
    72. list.Add(new Point(x, y));
    73. }
    74. }
    75. return list;
    76. }
    77. public List<Point> getWalkableNeighbors(Map karte)
    78. {
    79. List<Point> list = new List<Point>();
    80. int x = (int)this.position.X, y = (int)this.position.Y;
    81. for (int i = Math.Max(x - 1, 0); i < Math.Min(x + 2, karte.Tiles.GetUpperBound(0)); i++)
    82. {
    83. for (int j = Math.Max(y - 1, 0); j < Math.Min(y + 2, karte.Tiles.GetUpperBound(1)); j++)
    84. {
    85. if (i == x && j == y)
    86. {
    87. continue;
    88. }
    89. else if (karte.Tiles[i, j].begehbar)
    90. {
    91. list.Add(new Point(i, j));
    92. }
    93. }
    94. }
    95. return list;
    96. }
    97. }

    Objekt mit der Funktion einen Weg zu finden

    C#-Quellcode

    1. List<Knoten> findPathAStar(Vector2 start, Vector2 ziel)
    2. {
    3. // reset
    4. openList.Clear();
    5. closedList.Clear();
    6. Knoten zielKnoten = new Knoten (ziel);
    7. // end
    8. // Beginne mit Startpunkt A und füge ihn einer "offenen" Liste von Quadraten hinzu
    9. Knoten startKnoten = new Knoten(new Vector2(start.X, start.Y));
    10. openList.Add(startKnoten);
    11. // Füge sie der o.g. Liste hinzu und merke für jedes dieser Quadrate das Startquadrat A als seinen Vorgänger
    12. foreach (Point p in startKnoten.getWalkableNeighbors(karte)){
    13. Knoten ktn = new Knoten(new Vector2(p.X, p.Y));
    14. ktn.vorgaenger = startKnoten;
    15. openList.Add(ktn);
    16. }
    17. // Wirf das Startquadrat A aus der offenen Liste und füge es der geschlossenen Liste hinzu
    18. openList.removeWithPosition(startKnoten.position);
    19. closedList.Add(startKnoten);
    20. // Berechne alle Kosten aller Quadrate in der offenen Liste
    21. foreach (Knoten knt in openList)
    22. {
    23. if (knt.vorgaenger != null) { knt.G_GesamtFeldkosten = knt.vorgaenger.G_GesamtFeldkosten + knt.getDirectionalCostBasedOnPredecessor(); } else { knt.G_GesamtFeldkosten = 0; }
    24. knt.H_geschaetzteKosten = Math.Abs((int)(((ziel.X - start.X) + (ziel.Y - start.Y)) * 10));
    25. knt.F_StartZumZielUeberHierKosten = knt.G_GesamtFeldkosten + knt.H_geschaetzteKosten;
    26. }
    27. while (!zielKnoten.isInList(closedList))
    28. {
    29. // Das Quadrat mit den niedrigsten F-Kosten.
    30. Knoten aktuell = openList.getLowestF();
    31. // Entferne es aus der offenen Liste
    32. openList.removeWithPosition(aktuell.position);
    33. // füge es der geschlossenen Liste hinzu
    34. closedList.Add(aktuell);
    35. // Prüfe alle angrenzenden Quadrate und füge sie der offenen Liste hinzu (WENN: begehbar, nicht schon in offener/geschlossener Liste)
    36. foreach (Point p in aktuell.getWalkableNeighbors(karte))
    37. {
    38. Knoten ktn = new Knoten(new Vector2(p.X, p.Y));
    39. ktn.vorgaenger = aktuell;
    40. // Wenn nicht in geschlossener Liste
    41. if (!ktn.isInList(closedList))
    42. {
    43. // Wenn nicht in offener Liste
    44. if (!ktn.isInList(openList))
    45. {
    46. openList.Add(ktn);
    47. }
    48. // Wenn in offener Liste
    49. else
    50. {
    51. // WENN G-Wert für dieses Quadrat (ktn) wenn wir vom aktuellen dort hingehen [z. B. Recht und dann Hoch]
    52. int alterGWert = ktn.G_GesamtFeldkosten;
    53. int neueGWert = aktuell.G_GesamtFeldkosten + aktuell.getDirectionalCostOver(ktn);
    54. // Weg über (aktuell) wäre kürzer:
    55. if (neueGWert<alterGWert)
    56. {
    57. // Wir ändern den Vorgänger von ktn auf das aktuelle Quadrat
    58. ktn.vorgaenger = aktuell;
    59. // Berechnen die G-Kosten mit dem neuen Vorgänger sowie die neuen F-Kosten
    60. ktn.G_GesamtFeldkosten = ktn.vorgaenger.G_GesamtFeldkosten + ktn.getDirectionalCostBasedOnPredecessor();
    61. ktn.F_StartZumZielUeberHierKosten = ktn.G_GesamtFeldkosten + ktn.H_geschaetzteKosten;
    62. }
    63. }
    64. }
    65. }
    66. }
    67. List<Knoten> _path = new List<Knoten>();
    68. int indexOfZiel = zielKnoten.getIndexBasedOnPosition(closedList);
    69. Knoten tempKnoten = closedList[indexOfZiel];
    70. while (tempKnoten.vorgaenger != null)
    71. {
    72. _path.Add(tempKnoten);
    73. tempKnoten = tempKnoten.vorgaenger;
    74. }
    75. return _path;
    76. }

    Extensions

    C#-Quellcode

    1. static class MyExtensions
    2. {
    3. public static Knoten getLowestF(this List<Knoten> list)
    4. {
    5. Knoten kMitMinFKosten = list[0];
    6. for (int x = 0; x < list.Count - 1; x++){
    7. if (kMitMinFKosten.F_StartZumZielUeberHierKosten > list[x].F_StartZumZielUeberHierKosten){
    8. kMitMinFKosten = list[x];
    9. }
    10. }
    11. return kMitMinFKosten;
    12. }
    13. public static void removeWithPosition(this List<Knoten> list, Vector2 position)
    14. {
    15. for (int x = 0; x < list.Count - 1; x++)
    16. {
    17. if (list[x].position == position)
    18. {
    19. list.RemoveAt(x);
    20. }
    21. }
    22. }
    23. }


    Artentus
    @Artentus: Seit gestern ist doch noch ein andere "getDirectionalCost" hinzu gekommen, deswegen hab ich es wieder zur Funktion gemacht.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Eistee“ ()

    Also ich hätte das ja anders realisiert. Ein Knoten (warum eigentlich Deutsch?) sollte seine Nachbarn speichern, zusammen mit der Distanz zu ihnen. Das macht zwar den Speicheraufwand größer, erleichtert dir aber den Rest ungemein und verringert die benötigte Rechenleistung.
    Ich habe es nicht getestet, allerdings vermute ich in den Extensions einen Fehler:

    VB.NET-Quellcode

    1. For x As Integer = 0 To list.Count - 2

    Ich schätze dir geht ein Knoten durch die Lappen, vllt klappt es so besser

    VB.NET-Quellcode

    1. For x As Integer = 1 To list.Count - 1


    Edit: In C# wäre mir das nicht aufgefallen xDDD
    Ne, das kommt nicht vom Converter:

    Quellcode

    1. for (int x = 0; x < list.Count - 1; x++)

    ist gleichbedeutend mit

    VB.NET-Quellcode

    1. For x As Integer = 0 To list.Count - 2


    Und damit prüfst du das letzte Element in der Auflistung nie, dafür aber das Erste doppelt.
    @FreakJNS: Das erste wird doch nicht doppelt getestet?

    @Eistee: Ich nehme an, dass der Fehler war, dass du

    VB.NET-Quellcode

    1. list.Count - 1
    als

    C#-Quellcode

    1. list.Count - 1
    übernommen hast... Durch Converten ist dann halt -2 rausgekommen ^^

    Quellcode

    1. public static Knoten getLowestF(this List<Knoten> list)
    2. {
    3. Knoten kMitMinFKosten = list[0];
    4. for (int x = 0; x < list.Count - 1; x++){
    5. if (kMitMinFKosten.F_StartZumZielUeberHierKosten > list[x].F_StartZumZielUeberHierKosten){
    6. kMitMinFKosten = list[x];
    7. }
    8. }
    9. return kMitMinFKosten;
    10. }


    In Zeile 3 wird kMitMinFKosten auf das erste Element gesetzt. In der Schleife wird es im ersten Durchlauf dann mit sich selbst gegengeprüft, weil die Schleife eben von 0 anfängt. In VB sind Arrays 0-Basiert und da die For-Schleife bis zum letzten Index durchläuft muss man .Count-1 schreiben. In C# reicht ein x < .Count - da muss man nichts abziehen, weil da "kleiner als" steht und nicht "kleinergleich".