Vorrei capire a livello fondamentale il modo in cui funziona il * pathfinding. Qualsiasi implementazione di codice o psuedo-codice e visualizzazioni sarebbe utile.
Vorrei capire a livello fondamentale il modo in cui funziona il * pathfinding. Qualsiasi implementazione di codice o psuedo-codice e visualizzazioni sarebbe utile.
Risposte:
Ci sono tonnellate di esempi di codice e spiegazioni di A * che possono essere trovate online. Questa domanda ha anche ricevuto molte grandi risposte con molti link utili. Nella mia risposta proverò a fornire un esempio illustrato dell'algoritmo, che potrebbe essere più facile da capire rispetto al codice o alle descrizioni.
Per capire A *, ti suggerisco di dare prima un'occhiata all'algoritmo di Dijkstra . Lascia che ti guidi attraverso i passaggi che l'algoritmo di Dijkstra eseguirà per una ricerca.
Il nostro nodo di partenza è A
e vogliamo trovare il percorso più breve verso F
. A ciascun bordo del grafico è associato un costo di movimento (indicato da cifre nere accanto ai bordi). Il nostro obiettivo è valutare il costo di viaggio minimo per ciascun vertice (o nodo) del grafico fino a quando non raggiungiamo il nostro nodo obiettivo.
Questo è il nostro punto di partenza Abbiamo un elenco di nodi da esaminare, questo elenco attualmente è:
{ A(0) }
A
ha un costo di 0
, tutti gli altri nodi sono impostati su infinito (in un'implementazione tipica, questo sarebbe qualcosa di simile int.MAX_VALUE
o simile).
Prendiamo il nodo con il costo più basso dalla nostra lista di nodi (poiché la nostra lista contiene solo A
, questo è il nostro candidato) e visitiamo tutti i suoi vicini. Impostiamo il costo di ciascun vicino a:
Cost_of_Edge + Cost_of_previous_Node
e tenere traccia del nodo precedente (mostrato come una piccola lettera rosa sotto il nodo). A
può essere contrassegnato come risolto (rosso) ora, in modo da non visitarlo più. La nostra lista di candidati ora appare così:
{ B(2), D(3), C(4) }
Ancora una volta, prendiamo il nodo con il costo più basso dal nostro elenco ( B
) e valutiamo i suoi vicini. Il percorso verso D
è più costoso del costo attuale di D
, quindi questo percorso può essere scartato. E
verrà aggiunto alla nostra lista di candidati, che ora appare così:
{ D(3), C(4), E(4) }
Il prossimo nodo da esaminare è ora D
. La connessione a C
può essere scartata, poiché il percorso non è più breve del costo esistente. Tuttavia, abbiamo trovato un percorso più breve E
, pertanto il costo E
e il nodo precedente verranno aggiornati. La nostra lista ora appare così:
{ E(3), C(4) }
Quindi, come abbiamo fatto prima, esaminiamo il nodo con il costo più basso dal nostro elenco, che è ora E
. E
ha solo un vicino irrisolto, che è anche il nodo di destinazione. Il costo per raggiungere il nodo di destinazione è impostato su 10
e il nodo precedente su E
. La nostra lista di candidati ora appare così:
{ C(4), F(10) }
Successivamente esaminiamo C
. Siamo in grado di aggiornare il costo e il nodo precedente per F
. Poiché il nostro elenco ora ha F
come nodo il costo più basso, abbiamo finito. Il nostro percorso può essere costruito facendo il backtracking dei nodi più brevi precedenti.
Quindi potresti chiederti perché ti ho spiegato Dijkstra invece dell'algoritmo A * ? Bene, l'unica differenza è nel modo in cui pesi (o ordina) i tuoi candidati. Con Dijkstra è:
Cost_of_Edge + Cost_of_previous_Node
Con A * è:
Cost_of_Edge + Cost_of_previous_Node + Estimated_Cost_to_reach_Target_from(Node)
Dove Estimated_Cost_to_reach_Target_from
viene comunemente chiamata funzione euristica . Questa è una funzione che proverà a stimare il costo per raggiungere il nodo target. Una buona funzione euristica consentirà di visitare meno nodi per trovare l'obiettivo. Mentre l'algoritmo di Dijkstra si espanderebbe su tutti i lati, A * cercherà (grazie all'euristica) di cercare nella direzione del bersaglio.
La pagina di Eurit sull'euristica ha una buona visione d'insieme dell'euristica comune.
Un * percorso di ricerca è una ricerca di tipo primo in assoluto che utilizza un'euristica aggiuntiva.
La prima cosa che devi fare è dividere l'area di ricerca. Per questa spiegazione la mappa è una griglia quadrata di tessere, perché la maggior parte dei giochi 2D usa una griglia di tessere e perché è semplice da visualizzare. Si noti tuttavia che l'area di ricerca può essere suddivisa nel modo desiderato: una griglia esadecimale forse, o anche forme arbitrarie come Rischio. Le varie posizioni della mappa vengono denominate "nodi" e questo algoritmo funzionerà ogni volta che si ha un gruppo di nodi da attraversare e si hanno connessioni definite tra i nodi.
Ad ogni modo, partendo da una determinata tessera iniziale:
Le 8 tessere intorno alla tessera iniziale sono "segnate" in base a a) il costo del passaggio dalla tessera corrente alla tessera successiva (generalmente 1 per movimenti orizzontali o verticali, sqrt (2) per movimento diagonale).
A ogni tessera viene quindi assegnato un punteggio "euristico" aggiuntivo - un'approssimazione del valore relativo di spostamento su ogni tessera. Vengono utilizzate diverse euristiche, la più semplice è la distanza in linea retta tra i centri della piastrella data e la piastrella finale.
La tessera corrente viene quindi "chiusa" e l'agente si sposta sulla tessera vicina che è aperta, ha il punteggio di movimento più basso e il punteggio euristico più basso.
Questo processo viene ripetuto fino al raggiungimento del nodo obiettivo o non ci sono più nodi aperti (il che significa che l'agente è bloccato).
Per i diagrammi che illustrano questi passaggi, fare riferimento a questo buon tutorial per principianti .
Ci sono alcuni miglioramenti che possono essere fatti, principalmente nel migliorare l'euristica:
Tenendo conto delle differenze del terreno, della rugosità, della pendenza, ecc.
A volte è anche utile fare uno "sweep" attraverso la griglia per bloccare le aree della mappa che non sono percorsi efficienti: una forma a U rivolta verso l'agente, per esempio. Senza un test di spazzata, l'agente prima entrava nella U, si voltava, poi andava e si aggirava attorno al bordo della U. Un agente "reale" nota la trappola a forma di U e semplicemente la evita. Spazzare può aiutare a simulare questo.
È tutt'altro che il migliore, ma questa è un'implementazione che ho fatto di A * in C ++ qualche anno fa.
Probabilmente è meglio che ti indichi le risorse piuttosto che tentare di spiegare l'intero algoritmo. Inoltre, mentre leggi l'articolo della wiki, gioca con la demo e vedi se riesci a visualizzare come funziona. Lascia un commento se hai una domanda specifica.
L'articolo di ActiveTut su Path Finding potrebbe essere utile. Supera sia l'algoritmo di A * che Dijkstra e le differenze tra loro. È orientato verso gli sviluppatori Flash, ma dovrebbe fornire alcune buone informazioni sulla teoria anche se non si utilizza Flash.
Una cosa che è importante visualizzare quando si ha a che fare con A * e l'algoritmo di Dijkstra è che A * è diretto; cerca di trovare il percorso più breve verso un punto particolare "indovinando" quale direzione guardare. L'algoritmo di Dijkstra trova il percorso più breve verso / ogni / punto.
Quindi, come prima affermazione, A * è un algoritmo di esplorazione grafica. Di solito nei giochi utilizziamo le piastrelle o altre geometrie del mondo come grafico, ma puoi usare A * per altre cose. I due algoritmi ur per l'attraversamento dei grafici sono approfondimento prima ricerca e ampiezza prima ricerca. In DFS esplori sempre completamente il ramo corrente prima di guardare i fratelli del nodo corrente, e in BFS guardi sempre prima i fratelli e poi i figli. A * cerca di trovare una via di mezzo tra questi in cui esplori giù un ramo (quindi più simile a DFS) quando ti avvicini all'obiettivo desiderato, ma a volte fermati e prova un fratello se potrebbe avere risultati migliori lungo il suo ramo. La matematica attuale è che tieni un elenco di possibili nodi da esplorare in seguito dove ognuno ha una "bontà" punteggio che indica quanto vicino (in una sorta di senso astratto) è all'obiettivo, punteggi più bassi sono migliori (0 significherebbe che hai trovato l'obiettivo). Puoi scegliere quale usare successivamente trovando il minimo del punteggio più il numero di nodi di distanza dalla radice (che è generalmente la configurazione corrente o la posizione corrente nel pathfinding). Ogni volta che esplori un nodo aggiungi tutti i suoi figli a questo elenco e quindi scegli quello nuovo.
A livello astratto, A * funziona in questo modo: