Individuazione efficiente di molti nemici affollati attorno agli ostacoli


20

Sto lavorando per cercare di migliorare il percorso per i nemici del mio gioco. In questo momento, sostanzialmente si muovono costantemente verso la posizione esatta del giocatore calcolando l'angolo tra loro e i giocatori e muovendosi in quella direzione. Ho anche un algoritmo di floccaggio che impedisce ai nemici di impilarsi uno sopra l'altro, quindi si formeranno in gruppi anziché aggirarsi.

Tuttavia, ora che ho aggiunto una mappa basata su tessere, ad esempio ho bisogno che i nemici siano in grado di aggirare ostacoli e muri. Inizialmente ho provato ad aggiungere un valore di separazione alle tessere "non percorribili" in modo che l'algoritmo di floccaggio considerasse le pareti e gli ostacoli come oggetti da cui allontanarsi. Devo ancora capire se questo è fattibile o meno perché il mio test iniziale ha mostrato che i nemici colpivano un "muro" invisibile in cui non ci sono tessere non percorribili, eppure per qualche ragione, lo colpiscono e iniziano a spazzare via.

Mi chiedevo se potrebbe essere troppo pesante per calcolare un percorso verso il giocatore usando A * e quindi usare l'algoritmo di floccaggio per prevenire l'aggregazione. Inizialmente il mio gioco sarebbe stato uno sparatutto basato sull'onda, ma ho deciso invece di renderlo basato sul livello nella vena di Hotline Miami, quindi è probabile che avrò meno nemici, con l'orda occasionale, e farò più forti.

È una soluzione praticabile? Sto usando Java con Slick2D come motore di gioco. Oppure esiste una soluzione / algoritmo migliore che affronta entrambi questi problemi?


7
Come ho descritto nella modifica, "è troppo pesante" è una domanda da porre al tuo profiler, perché dipenderà dalla tua implementazione, dall'hardware di destinazione, dal budget delle prestazioni e dal contesto del tuo gioco - tutto ciò che tu e il tuo profiler conoscete intimamente ma gli estranei di Internet no. Se vuoi ottenere il pathfinding dei greggi in modo efficiente, possiamo suggerire strategie per aiutarti, ma solo la tua profilazione può rispondere a ciò che è abbastanza efficiente per le tue esigenze. Se si profila e identifica un problema di prestazioni specifico, possiamo anche aiutarti a trovare come risolverlo.
DMGregory

1
Il modo in cui le implementate influisce sulle prestazioni. Ad esempio, eseguendo solo A * sui leader e facendo affidamento sul floccaggio per i follower.
Pikalek,

Se il tuo gioco si basa principalmente sulla lotta contro questi nemici, l'algoritmo che prenderai avrà un impatto enorme su come si sente il gioco. Quindi dovresti provare approcci diversi, ad esempio ti sembra che i nemici conoscano perfettamente il livello e la posizione del giocatore in ogni momento e lo seguono come diretto da un'intelligenza artificiale onnisciente? - altri approcci potrebbero essere quelli di far correre i nemici nella direzione generale in cui il giocatore ha fatto rumore e solo sulla linea di vista diretta correre verso di lui, o gridare e informare gli altri nemici dove si trova il giocatore ...
Falco

@Falco Dato che il gioco non è più basato sulle onde e sarà basato sul livello, e dal momento che i nemici sono zombi ... stavo pensando di farlo in modo che tu debba essere visto o fare rumore per trovarti. Quindi se usi un'arma rumorosa? Emette un suono in un raggio e tutti i nemici nel raggio di portata verso la posizione del suono emesso, e quindi si muoveranno casualmente intorno a quell'area.
Darin Beaudreau, il

Risposte:


49

Sembra un caso d'uso per Flow Fields.

In questa tecnica, esegui una singola query di ricerca dei percorsi verso l'esterno dagli oggetti del giocatore, contrassegnando ogni cella che incontri con la cella da cui l'hai raggiunta.

Se tutte le piastrelle / i bordi hanno lo stesso costo di attraversamento, è possibile utilizzare una semplice ricerca per ampiezza per questo. Altrimenti, l'algoritmo di Dijkstra (come A * senza obiettivo / euristico) funziona.

Questo crea un campo di flusso: una tabella di ricerca che associa ogni cella al passaggio successivo verso l'oggetto giocatore più vicino da quella posizione.

Ora i tuoi nemici possono cercare la loro posizione attuale nel campo del flusso per trovare il passo successivo nel loro percorso più breve per evitare gli ostacoli verso l'oggetto giocatore più vicino, senza che ciascuno faccia la propria query di ricerca del percorso.

Questo scala sempre meglio i nemici che hai nel tuo gregge. Per un singolo nemico, è più costoso di A * perché cerca in tutta la mappa (anche se puoi iniziare presto una volta che hai raggiunto tutti gli agenti di tracciamento). Ma quando aggiungi più nemici, questi possono condividere sempre più i costi di tracciamento calcolando i segmenti di percorso condivisi una volta anziché ripetutamente. Ottieni anche un vantaggio dal fatto che i BFS / Dijkdtra sono più semplici di A * e in genere più economici da valutare per cella ispezionata.

Esattamente dove colpisce il punto di pareggio, da A * più economico, ad A * con la memoizzazione più economica (dove riutilizzi alcuni dei risultati per una query di pathfinding precedente per accelerare il successivo), a campi di flusso che vengono più economico, dipenderà dalla tua implementazione, dal numero di agenti e dalle dimensioni della tua mappa. Ma se mai pianifichi un grande sciame di nemici che si avvicinano da più direzioni in un'area ristretta, un campo di flusso sarà quasi sicuramente più economico di A *.

A titolo di esempio estremo, puoi vedere un video qui con 20.000 agenti tutti contemporaneamente in una griglia ragionevolmente piccola .


Questa tecnica sembra davvero pulita. Io lo verificherò.
Darin Beaudreau,

15
È possibile utilizzare un algoritmo ibrido che costruisce un campo di flusso parziale senza cercare più della mappa di quanto farebbero ripetute chiamate ad A * e non cercare mai la stessa posizione due volte. L'idea di base è scegliere un nemico arbitrario e iniziare una ricerca A * dal giocatore verso quel nemico, segnando le cellule mentre le incontri proprio come nella normale generazione del campo di flusso. Una volta che la ricerca trova quel nemico, scegli un altro nemico (che non hai ancora trovato) come bersaglio, riordina il set aperto in base alla nuova euristica e continua a cercare. Fermati quando hai trovato tutti i nemici.
Ilmari Karonen,

1
Che dire di evitare le collisioni? Questo è (un po ') menzionato nell'OP (evitando il clipping quando raggiungono il giocatore). Mi sembra che dovresti ripetere tutti i djikstras ogni volta che qualcosa si muove (o aggiungi qualche logica aggiuntiva)
Marte

2
@Mars L'OP parla del floccaggio, quindi suppongo che tutti gli individui possano muoversi alla stessa velocità; gli unici luoghi in cui le collisioni saranno un problema sono i colli di bottiglia, che richiedono che parte del gregge si fermi e attenda. Tuttavia, non ha davvero bisogno di cambiare il pathfinding - una semplice coda probabilmente funzionerebbe abbastanza bene nella maggior parte dei casi, e una certa distorsione del percorso (una selezione pseudo-casuale di percorsi alternativi con costi simili) funzionerà per produrre un gregge dall'aspetto più naturale flussi che evitano anche che l'intero stormo cerchi di attraversare un particolare spazio a tessera singola :)
Luaan,

3
@Luaan In un gioco basato su tessere, rimarrai sorpreso dalla frequenza delle collisioni. Personalmente, trovo che l'opzione "accodamento" sia meno che ottimale. Inoltre, se le unità non possono attraversarsi, dovrai ricalcolare quando le unità iniziano a raggiungere la loro posizione finale e un mucchio di altri casi limite. Floccaggio è difficile;)
Mars

8

A * non ha prestazioni elevate. Mi avvicinerei a questa situazione variando gli algoritmi. Esegui A * di tanto in tanto e tra una volta e l'altra controlla se il passaggio successivo è gratuito o se hai bisogno di evasione.

Ad esempio, traccia la distanza dei giocatori dalla posizione target A *, se è sopra una soglia ricalcola un * e poi aggiorna i movimenti. La maggior parte dei giochi utilizza una combinazione di waypoint, ad esempio una griglia semplificata per la ricerca del percorso e una logica che gestisce il movimento tra i waypoint con algoritmi di governo dell'evasione mediante i raycast. A mio avviso, gli agenti cercano di correre verso un waypoint distante manovrando attorno agli ostacoli nelle loro vicinanze.

È meglio lavorare con macchine a stati finiti qui e leggere il libro "Programmazione di AI per esempio" di Mat Buckland. Il libro offre tecniche comprovate per il tuo problema e dettaglia la matematica richiesta. Il codice sorgente del libro è disponibile sul web; il libro è in C ++ ma sono disponibili alcune traduzioni (incluso Java).


2
Con un approccio A * che si aggiorna raramente, può essere utile scaglionare gli aggiornamenti, mantenendo un budget per il numero di nemici autorizzati a ripetere il percorso su un singolo frame. In questo modo è possibile limitare il costo di ricerca del percorso massimo per fotogramma e gestire in modo più efficace molti percorsi di intelligenza artificiale ammortizzando il loro costo totale su più fotogrammi. Un'intelligenza artificiale che utilizza un percorso non aggiornato per un fotogramma o due quando il budget per il fotogramma è stato superato o che ricade su calcoli morti se vicini, di solito non sarà dirompente.
DMGregory

2
Probabilmente dichiarando l'ovvio qui, ma se aggiornerai solo alcuni dei tuoi percorsi in un determinato frame, potresti voler un sistema prioritario basato sulla distanza dal giocatore. È probabilmente più importante per i nemici vicino al giocatore aggiornare i loro percorsi, mentre probabilmente è OK per i nemici lontani usare un percorso stantio.
AC

4

Non solo è fattibile, credo che sia stato realizzato in un gioco commerciale negli anni '90: BattleZone (1998).

Quel gioco aveva unità 3D con movimento libero non basato su tessere e costruzione base basata su tessere.

Ecco come sembrava funzionare:

Innanzitutto, A * o qualcosa di simile (probabilmente una variazione di A * con limiti rigorosi su quanto tempo può trovare un percorso, quindi non richiede mai troppe risorse per essere eseguito ma non trova sempre un percorso fino alla destinazione) sarebbe usato per trovare un percorso per un hovertank per arrivare a destinazione senza rimanere bloccato in ostacoli basati su piastrelle.

Quindi il carro armato volerebbe in giro fino allo spazio come se fosse attratto dal centro di una tessera vicina sul suo percorso e respinto da ostacoli, altri carri armati vicini, ecc.


1
Quindi qual è un buon modo per gestire seguendo il percorso, ma non esattamente? Se acconsento a deviare in curva, devo essere in grado di impedire ai nemici di scontrarsi con l'angolo di un ostacolo. Devo mantenere il comportamento del gregge sia per i nemici che per gli ostacoli e aggiungere A * per affrontare quelle situazioni?
Darin Beaudreau, il
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.