Un * algoritmo per i giochi di ruolo tattici?


15

Sto scherzando con la scrittura di un gioco di ruolo tattico davvero scadente in C ++. Finora ho una mappa di tessere 2D e ho appena fatto funzionare l'algoritmo A * basato sullo pseudocodice in Wikipedia .

Ma i veri giochi di ruolo tattici non solo trovano il percorso migliore su un piano piatto e si spostano lì. Di solito hanno intervalli di movimento limitati e devono salire o scendere. Se hai mai giocato a Final Fantasy Tactics, queste sarebbero influenzate dalle statistiche Move e Jump. Questo è dove mi perdo. Come posso modificare l'algoritmo A * in modo che trovi il percorso migliore verso un bersaglio, ma il percorso è lungo solo un numero di tessere? Come devo prendere in considerazione le differenze di altezza e saltare le statistiche? Come posso implementare saltare un gap?

Se aiuta, in questo momento la mia mappa è rappresentata da un vettore di oggetti Tile. Ogni riquadro ha puntatori al riquadro Nord, Sud, Est e Ovest, che sono impostati su nullptr se non esiste alcun riquadro, ad esempio lungo il bordo della mappa o se un riquadro è impostato su non passabile.


5
Non so perché spostare l'intervallo sia un problema. Trova il percorso più breve e, successivamente, sposta i quadrati di "velocità" lungo quel percorso.
Mooing Duck,

Risposte:


33

L'arrampicata e le lacune sono solo diverse funzioni di costo. Per un'unità che può saltare il divario ha un costo normale (?), Mentre per un'unità che non salta ha un costo arbitrariamente alto. Costi di arrampicata extra, così come terreni difficili, ecc. L'algoritmo A * è in grado di gestire le funzioni di costo, quindi se l'implementazione non lo sta già facendo, basta cercare su Google come implementare A * con una funzione di costo.

Detto questo, tuttavia, non credo che A * sia un approccio particolarmente valido per un gioco di ruolo tattico. O più precisamente, non è una storia completa. Non vuoi che le tue unità sbagliano alla cieca verso il loro obiettivo, vuoi che si posizionino per sfruttare la copertura, l'altura, qualunque cosa, mentre avanzano verso l'obiettivo finale e cercano di affiancare gli avversari e così via. Quindi il valore tattico del punto finale di ogni mossa è di enorme importanza, non solo quanto vicino è l'obiettivo. Ciò richiede una risoluzione dei problemi più approfondita rispetto alla semplice ricerca di percorsi.


10
Aspetti positivi riguardo al "posizionamento tattico", ma tali decisioni potrebbero essere applicate a un livello superiore rispetto al pathfinding di base. D'altra parte, applicare i costi ai nodi dell'algoritmo di pathfinding generati da una sorta di analizzatore tattico potrebbe essere una buona opzione. Ad esempio, se il nemico ha una linea di vista sul terreno, allora i nodi su quel terreno hanno un costo molto elevato.
DrMcCleod,

1
@DrMcCleod: In effetti, e questo è ciò che intendevo per "O più precisamente, non è una storia completa". Potresti certamente utilizzare A * o un altro algoritmo per eseguire parte dell'elaborazione, tuttavia penso che eviterei approcci come il tentativo di evitare nodi osservati attraverso i costi di movimento poiché lo spostamento su un terreno in cui un'unità potrebbe venire colpita è meglio gestito come un calcolo rischio / rendimento, IMO.
Jack Aidley,

13

Quando vuoi tutte le possibili opzioni di movimento di un'unità, usa l'algoritmo di Dijkstra .

La differenza tra A * e Dijkstra è che Dijkstra ti offre tutti i percorsi più brevi possibili raggiungibili con un dato costo, e se nessuno di essi raggiunge ancora la destinazione, aumenta il costo di uno e continua. A *, d'altra parte, preferisce calcolare prima quei percorsi che hanno buone probabilità di raggiungere la destinazione.

Quindi quando vuoi solo il percorso più breve dal punto A al punto B, allora A * è una buona scelta. Ma se vuoi tutte le possibili opzioni di movimento e il percorso più breve per ognuna di esse, Dijkstra è esattamente quello che vuoi.

Tutto quello che devi fare è eseguire l'algoritmo di Dijksta senza un nodo di destinazione specifico, ma con un costo massimo che non deve essere superato (il raggio di movimento dell'unità). Quando si viaggia in un nodo supererebbe il costo massimo, non visitarlo. Quando l'algoritmo termina a causa della mancanza di bordi non visitati, ogni nodo nel set visitato è una destinazione possibile e i marker di nodo precedenti dei nodi formano un elenco collegato che rappresenta il percorso di ritorno al nodo iniziale.

Per quanto riguarda i salti: quelli possono essere rappresentati come ancora un altro vantaggio sia in A * che Dijkstra. Possono avere lo stesso costo di attraversare un bordo normale o uno diverso. È inoltre possibile passare un parametro "jump_height" all'algoritmo che indica all'algoritmo di ignorare i jump-edge che superano una determinata altezza.


9
Vale la pena menzionare qui che in A*realtà è solo una generalizzazione di Dijkstra, quindi se capisci uno non dovrebbe essere troppo difficile capire l'altro.
Cubico

8
In effetti, se hai un'euristica che restituisce solo 0 nel tuo algoritmo A *, congratulazioni! Hai appena scritto l'algoritmo di Dijkstra.
Yann,

9
"Dijkstra ti offre tutti i percorsi più brevi possibili raggiungibili con un dato costo, e se nessuno di essi raggiunge ancora la tua destinazione, aumenta il costo di uno e continua" - Non è né il modo in cui funziona né ciò che produce. In realtà è solo una generalizzazione dell'ampiezza della ricerca per grafici ponderati. Trova un singolo percorso più breve. A * è solo una generalizzazione di ciò, poiché quando hai a disposizione un'euristica a distanza.
BlueRaja - Danny Pflughoeft

1
Non so perché sia ​​così votato. Da un punto di vista pragmatico, Dijkstra è obsoleto. Viene insegnato in CS per motivi educativi e storici. Anche A * è obsoleto per lavori seri; Google Maps certamente non lo utilizza. Oggi guarderesti le varianti di ArcGraph.
MSalters il

2
@MSalters Dijkstra e A * sono algoritmi perfettamente perfetti per problemi semplici come i giochi di ruolo tattici. Vi è una gamma molto ristretta di movimenti validi (tessere) e una quantità molto limitata di modi per spostarsi tra le tessere (orthagon, a volte diagonale) e di solito un percorso massimo abbastanza breve: SQRT (ArenaWidth² * ArenaHeight²). Dal punto di vista computazionale, la differenza è trascurabile su una macchina moderna per quali sono probabilmente i valori abbastanza piccoli, quindi perché preoccuparsi di un'implementazione più complessa quando una semplice è sufficiente per gli scopi delineati qui?
Valthek,

2

Le altre risposte hanno alcune buone informazioni, quindi assicurati di leggerle.

Tuttavia, per rispondere alla tua domanda: in base allo pseudocodice a cui ti sei collegato, hai una sorta di funzione in heuristic_cost_estimatecui stai calcolando il costo da tileA a tileB (supponendo che siano adiacenti). Invece di usare un flat (1) per quel costo, dovresti modificarlo per includere le statistiche delle tessere e le statistiche delle unità, e possibilmente le statistiche sui bordi.

Per esempio:

if (edge == JUMP && !unit.canJump()) 
    return INF;
if (tile.Type == Forest && unit.moveType == HORSE) 
    return 2;
//Other cases here
//-----
else 
    return 1;

Questo ti darà il tuo percorso. Quindi, dovrai semplicemente spostare l'unità lungo il loro percorso mentre consumano punti movimento e fermarli quando rimangono Punti <edgeCost. Nota che questo potrebbe non essere del tutto ottimale se finisci con rimanendo Punti = 1, ma dovrebbe essere abbastanza buono per un gioco di ruolo pratico. In realtà, vorresti più tattico, come ha sottolineato Jack Aidley!

Sfida:
se vuoi diventare più avanzato, probabilmente vuoi usare Djikstras come suggerito per trovare tutti gli spazi entro la distanza X, quindi vuoi valutare ogni spazio in quella lista per un posto "migliore", in base alla vicinanza al bersaglio, alla difesa potere, indipendentemente dal fatto che tu possa essere attaccato o meno da quella posizione, ecc. In base a tali informazioni, sceglieresti una tessera, quindi ti sposteresti seguendo il percorso che hai appena calcolato usando Djikstras.


1

L'arrampicata e le lacune sono piuttosto banali poiché modificano solo i costi. Il pathfinding (e la maggior parte dell'intelligenza artificiale tattica) consiste nel sommare il costo su tutti i nodi da visitare e minimizzarlo. Una scogliera invalicabile avrà un costo infinito (molto, molto alto), le pendenze avranno un costo più elevato del normale, ecc.

Questo, tuttavia, trova il percorso ottimale a livello globale che non è la soluzione migliore perché i veri avversari di solito non trovano il percorso ottimale. È altamente irrealistico, a volte fino a un punto che è ovvio per il giocatore, e fastidioso (specialmente quando l'IA in quanto tale è sostanzialmente invincibile perché anch'essa sceglie l'ottimale).

Le buone simulazioni non trovano deliberatamente il percorso migliore. Un algoritmo molto migliore potrebbe essere la ricerca gerarchica di percorsi - se non altro, tracciando una linea retta sulla mappa e prendendo 4-5 waypoint, quindi la navigazione da un waypoint a quello successivo, considerando solo i pesi dei nodi che sono così lontani noto e impostando tutti gli altri pesi del nodo su "indifferenti". In alternativa, è possibile eseguire prima A * su una griglia più grossolana, quindi il percorso da un nodo grande a quello successivo (ma suppongo che anche disegnare una linea sulla mappa vada bene).

Questo è molto più realistico (e consuma anche una frazione della potenza di elaborazione perché il grafico è molto più piccolo). Sì, può significare che un'unità si sposta verso una scogliera solo per scoprire che non può attraversare. Va bene, succede anche ai veri avversari. La prossima volta non accadrà più (perché ora è noto il costo infinito).


1
Intendiamoci, che il "semplice tracciamento gerarchico" può sembrare piuttosto stupido. Ottieni unità che camminano direttamente su una dorsale montuosa, solo per scoprire lì che il percorso è bloccato. Quindi attraversano il valico fino al prossimo waypoint, e da lì alla loro destinazione, anche se quest'ultimo waypoint era fuori rotta per loro. La preelaborazione avrebbe identificato il passo di montagna in primo piano e segnalato da lì. Ma anche se non lo fai, una volta che sei troppo lontano dal corso pianificato, dovresti riprogrammare il resto.
MSalters il

@MSalters: questo può accadere con il metodo "disegna una linea" anche dopo il primo tentativo, sì. È improbabile che accada più di una volta con il metodo gerarchico a griglia grossolana (che ad esempio prende la media, o la mediana, o anche il valore di costo massimo dei nodi all'interno). È praticamente esattamente come giocherebbe un avversario umano: evita i grandi ostacoli che conosci o puoi vedere da lontano come una catena montuosa, e altrimenti pianifica un percorso grossolano per lo più dritto e morditi. A meno che tu non sappia che c'è una montagna, vai dritto verso di essa.
Damon,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.