Il tracciato precompilato è ancora rilevante?


12

Contesto

I vecchi giochi di avventura grafica punta e clicca di Lucas Arts (ScummVM era) utilizzavano percorsi precompilati. Ecco uno schema generale della tecnica.

Passo 1

Il pavimento di ogni stanza era diviso in quelle che chiamavano "walk box", che erano praticamente equivalenti ai nodi in una rete di navigazione, ma limitate alle forme trapezoidali. Per esempio:

 ______ _____ _________ _____
\   A  |  B  |    C    |  D   \
 \_____|     |         |_______\
       |_____|         |
             |_________|

Passo 2

Un algoritmo offline (ad es. Dijkstra o A *) calcolerebbe il percorso più breve tra ciascuna e ogni coppia di nodi e memorizzerebbe il primo passaggio del percorso in una matrice 2D, indicizzato in ogni dimensione dal nodo iniziale e finale utilizzato. Ad esempio utilizzando le walk box sopra:

      ___ ___ ___ ___
     | A | B | C | D | <- Start Node
  ___|___|___|___|___|
 | A | A | A | B | C |  ---
 |___|___|___|___|___|     |
 | B | B | B | B | C |     |
 |___|___|___|___|___|     |-- Next node in shortest path
 | C | B | C | C | C |     |   from Start to End
 |___|___|___|___|___|     | 
 | D | B | C | D | D |  ---
 |___|___|___|___|___| 
   ^
   |
End Node

Come si può immaginare, i requisiti di memoria aumentano rapidamente all'aumentare del numero di nodi (N ^ 2). Dato che un corto sarebbe di solito abbastanza grande da memorizzare ogni voce nella matrice, con una mappa complessa di 300 nodi che comporterebbe la memorizzazione di un extra:

300^2 * sizeof(short) = 176 kilobytes

Passaggio 3

D'altra parte, calcolare il percorso più breve tra due nodi era estremamente veloce e banale, solo una serie di ricerche nella matrice. Qualcosa di simile a:

// Find shortest path from Start to End
Path = {Start}
Current = Start
WHILE Current != End
    Current = LookUp[Current, End]
    Path.Add(Current)
ENDWHILE

Applicando questo semplice algoritmo per trovare il percorso più breve da C ad A, si ottiene:

1) Path = { C }, Current = C
2) Path = { C, B }, Current = B
3) Path = { C, B, A }, Current = A, Exit

Domanda

Sospetto che con il potente hardware di oggi, unito ai requisiti di memoria di farlo per ogni livello, tutti i vantaggi che questa tecnica aveva una volta sono ora superati semplicemente eseguendo un A * in fase di esecuzione.

Ho anche sentito che al giorno d'oggi le ricerche di memoria potrebbero anche essere più lente del calcolo generale, motivo per cui la creazione di tabelle di ricerca seno e coseno non è più così popolare.

Ma devo ammettere che non sono ancora troppo informato su queste questioni di efficienza hardware di basso livello, quindi sto cogliendo l'occasione per chiedere l'opinione di coloro che hanno più familiarità con l'argomento.

Sul mio motore avevo anche bisogno della possibilità di aggiungere e rimuovere dinamicamente nodi al grafico in fase di esecuzione ( vedi questo ) in modo che il percorso pre-calcolato rendesse solo le cose più complicate, quindi l'ho scartato (per non parlare del mio tempo di esecuzione Una soluzione * stava già funzionando perfettamente ). Tuttavia, sono rimasto a chiedermi ...

In conclusione, questa tecnica è ancora rilevante al giorno d'oggi in qualsiasi scenario?


2
Penso che sia ancora rilevante se hai un budget limitato per la CPU. Ma una volta che vuoi percorsi dinamici non è più utile. A proposito, ho visto da dove hai preso il tuo algoritmo A * e puoi ottimizzarlo ulteriormente usando un minheap e altri trucchi. Ho fatto alcune iterazioni per migliorare A * in C # che puoi vedere qui: roy-t.nl/index.php/2011/09/24/… potrebbe essere utile.
Roy T.

1
Grazie, l'ho aggiunto ai segnalibri e lo esaminerò quando inizierò a ottimizzare la mia applicazione. Ho praticamente usato la soluzione di Eric Lippert con alcune piccole modifiche perché era così pulito e facile da seguire ... E per tutti i miei casi di test ha funzionato praticamente "istantaneamente", quindi non mi sono nemmeno preoccupato di ottimizzarlo.
David Gouveia,

1
A proposito, se decidi di perseguire il pre-calcolo, potresti voler esaminare l' algoritmo di Floyd-Warshall . Costruisce la matrice del "passo successivo" in modo più efficiente rispetto a utilizzare ripetutamente Dijkstra / A *.
entro il

@amitp Grazie per il suggerimento, è sempre bene conoscere queste alternative! Sebbene, poiché nella maggior parte dei casi il pre-calcolo sarebbe effettuato offline, non ci sarebbe molto da guadagnare rendendolo più efficiente. A meno che tu non sia davvero impaziente. :-)
David Gouveia,

D'accordo, anche se Floyd-Warshall è anche molto più semplice da implementare rispetto all'algoritmo di Dijkstra, quindi se non hai già implementato Dijkstra, vale la pena dare un'occhiata :)
tra l'

Risposte:


3

Sul mio motore avevo anche bisogno della possibilità di aggiungere e rimuovere dinamicamente nodi al grafico in fase di esecuzione (vedi questo) in modo che il percorso pre-calcolato rendesse solo le cose più complicate, quindi l'ho scartato (per non parlare del mio tempo di esecuzione Una soluzione * stava già funzionando perfettamente ). Tuttavia, sono rimasto a chiedermi ...

In conclusione, questa tecnica è ancora rilevante al giorno d'oggi in qualsiasi scenario?

Non vedo alcun beneficio dall'utilizzare una tale tecnica.

Manca la flessibilità di un grafico (puoi avere LOD diversi, non devono avere una forma specifica, ecc ...). Inoltre, qualsiasi utente del tuo motore saprà cos'è un grafico e come utilizzarlo. Quindi, se vogliono aggiungere funzionalità extra, dovranno imparare come implementare la loro estensione usando una situazione completamente nuova per loro.

Come hai detto sembra che si ridimensionerebbe in modo orribile. Vale anche la pena notare che se un grafico si adatta ai contanti e si eseguono tutti i risultati del percorso back to back, si riduce davvero il tempo di IO. Sembra che l'implementazione diventerà presto troppo grande per adattarsi a qualsiasi cache.

Ho anche sentito che al giorno d'oggi le ricerche di memoria potrebbero anche essere più lente del calcolo generale, motivo per cui la creazione di tabelle di ricerca seno e coseno non è più così popolare.

A meno che tu non riesca ad adattare tutto il tuo programma e la sua memoria di cui ha bisogno nella cache, finirai per collocare le cose dentro e fuori dalla memoria molto prima di collocare il collo nel processore.

Sospetto che con l'hardware potente di oggi, unito ai requisiti di memoria di farlo per ogni livello, tutti i vantaggi che questa tecnica aveva una volta sono ora superati semplicemente eseguendo un A * in fase di esecuzione

Comprendi anche che molti giochi hanno loop separati per l'aggiornamento dell'IA. Credo che il modo in cui è impostato il mio progetto sia che esiste un ciclo di aggiornamento per l'input dell'utente a 60 Hz, l'intelligenza artificiale è di soli 20 Hz e il gioco si disegna il più rapidamente possibile.

Inoltre, come nota a margine, ho fatto un po 'di programmazione GBA solo per divertimento e niente è passato all'utilizzo di un dispositivo moderno. Per il GBA tutto consisteva nel ridurre al minimo il carico di lavoro del processore (perché era patetico). Devi anche capire che la maggior parte dei linguaggi di alto livello C # e Java (non tanto C ++ o C) fanno tonnellate di ottimizzazioni per te. Per quanto riguarda l'ottimizzazione del codice, il loro non è molto altro da fare se non quello di accedere alla memoria il meno possibile e quando si eseguono il maggior numero possibile di calcoli su di esso prima di introdurre nuova memoria che lo eliminerà dalla cache e si assicurerà che lo sia fare le cose solo una volta.

Modifica: anche per rispondere al tuo titolo sì, lo è. Il calcolo preliminare dei percorsi utilizzati di frequente è un'ottima idea e può essere eseguito con A * ovunque al di fuori del ciclo di gioco. Ad esempio dalla tua base a una risorsa in un RTS in modo che i raduni non debbano ricalcolare ogni volta che vogliono partire o tornare.


A proposito della tua modifica, non stavo davvero parlando del precomputer dei percorsi utilizzati di frequente, ma strettamente della tecnica delineata per precomputer ogni singolo percorso possibile . Sono anche un po 'confuso su come la maggior parte della tua risposta è stata contraria all'utilizzo del pathfinding precompilato, ma poi alla fine hai detto che sarebbe stata un'idea eccellente. Quindi, sarebbe utile in un ambiente con CPU limitata come GBA?
David Gouveia,

1
Mio male, stavo cercando di sottolineare che la risposta al tuo titolo estratta dal contesto è sì. Mentre la risposta relativa all'algoritmo specifico descritto nella tua domanda è no. Quindi in breve pre-calcolo tutti i possibili percorsi sono una cattiva idea ma precompilare alcuni percorsi usati molto frequentemente può essere una buona idea.
ClassicThunder

2
@ClassicThunder: questa tecnica di pre-calcolo di tutti i percorsi da alcuni punti di riferimento viene spesso definita ALT : A-star con punti di riferimento e disuguaglianza del triangolo : cs.princeton.edu/courses/archive/spr06/cos423/Handouts/GW05. pdf
Pieter Geerkens,
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.