Come calcolare rapidamente l'area di visualizzazione in un gioco piastrellato 2D?


24

Ho una matrice di tessere, su alcune di quelle tessere ci sono oggetti. Voglio calcolare quali tessere sono visibili al giocatore e quali non lo sono, e devo farlo in modo abbastanza efficiente (quindi calcolerebbe abbastanza velocemente anche quando ho una grande matrice (100x100) e molti oggetti).

Ho provato a farlo con l'algoritmo di linea di Bresenham , ma è stato lento. Inoltre, mi ha dato alcuni errori:

----XXX-        ----X**-     ----XXX-
-@------        -@------     -@------
----XXX-        ----X**-     ----XXX-
(raw version)   (Besenham)   (correct, since tunnel walls are 
                              still visible at distance)

(@ is the player, X is obstacle, * is invisible, - is visible)

Sono sicuro che questo può essere fatto - dopo tutto, abbiamo NetHack, Zangband, e tutti hanno affrontato questo problema in qualche modo :)

Quale algoritmo puoi consigliare per questo?


Per le mie esigenze, definirò visibile in questo modo: la tessera è visibile quando almeno una parte (ad es. Angolo) della tessera può essere collegata al centro della tessera giocatore con una linea retta che non interseca nessuno degli ostacoli.


1
Whoops, mio ​​errore, NetHack non stava scherzando con la linea di vista :)
Rogach

Alcune idee più vecchie possono essere trovate su fadden.com/tech/fast-los.html , anche se ciò risale ai giorni in cui le CPU erano abbastanza lente e i calcoli in virgola mobile erano qualcosa di meglio da evitare.
fadden,

Risposte:


10

La tua definizione di visibile è la seguente:

la tessera è visibile quando almeno una parte (es. angolo) della tessera può essere collegata al centro della tessera giocatore con una linea retta che non interseca nessuno degli ostacoli

Puoi implementare questo concetto letteralmente tracciando i raggi dalla tessera del tuo giocatore e intersecandoli con la tua scena. Interrompi da ogni iterazione quando il raggio colpisce un ostacolo (o supera una certa soglia di distanza) poiché sei interessato solo alle tessere che il giocatore può vedere direttamente. Spezzerò il processo per te:

  1. Specifica il livello di precisione che desideri fornire all'algoritmo. Questo sarà il numero di raggi che tracciamo.
  2. Dividi l'intero cerchio di 360 gradi per la precisione scelta per sapere di quanti gradi ruotare tra ciascun raggio.
  3. Partendo da 0 gradi e aumentando della quantità determinata nel passaggio 2, crea un raggio con l'origine al centro della tessera giocatore e la direzione determinata dall'angolo corrente.
  4. Per ogni raggio, partendo dalla tessera giocatore, cammina lungo la direzione del raggio fino a colpire una tessera ostacolo. Aggiungi quel riquadro all'elenco dei riquadri visibili e passa al raggio successivo. Potresti anche voler aggiungere una distanza massima per "arrenderti" nel caso in cui non venga rilevata alcuna collisione.

Ecco un'immagine che mostra 3 raggi di esempio. Le tessere colorate più scure sono il "risultato" di ogni raggio, ovvero dove si è verificata la collisione. Dovresti ripetere tutto questo intorno al cerchio però:

inserisci qui la descrizione dell'immagine

Modifica la distanza massima e il numero di raggi per le prestazioni. Troppo poco e ti mancheranno le tessere, troppo e la tua performance ne risentirà. Inoltre, più lontano devono viaggiare i raggi, maggiore sarà l '"errore" e maggiore sarà la precisione.

modificare

Controlla il seguente tutorial sul raycasting, in particolare i passaggi 3 e 4, per aiutarti a implementare il bit di intersezione dell'algoritmo:

http://www.permadi.com/tutorial/raycast/rayc7.html


Dovrei semplicemente "camminare" lungo ogni raggio di una distanza fissa (diciamo 0,3 punti) o devo eseguire qualcosa come l'algoritmo di Besenham su ciascun raggio?
Rogach,

Se avanzi solo di una distanza fissa, avrai problemi con le tessere perse. Controllare questo tutorial su raycasting . Modificherò tale risorsa anche nella mia risposta. Fondamentalmente si controlla per collisioni orizzontali e verticali separatamente.
David Gouveia,

1
L'algoritmo è buono, ma richiederà un'enorme quantità di raggi per funzionare correttamente con lunghi tunnel di 1 piastrella.
HolyBlackCat

@HolyBlackCat - sarà il caso solo se invii raggi ad angoli pari in tutte le direzioni. Ma puoi evitare di inviare la maggior parte di quei raggi e lanciarli solo alle estremità della linea nella tua scena. Ecco una buona spiegazione: redblobgames.com/articles/visibility
Rogach

8

Preferirei lanciare raggi d'ombra anziché raggi di linea di vista.

Supponiamo che questa sia la tua area di visualizzazione (l'area potenzialmente visibile)

######################
#####.............####
###................###
##..................##
#....................#
#....................#
#..........@.........#
#....................#
#....................#
##..................##
###................###
#####.............####
######################

I blocchi # non sono visibili mentre il. sono visibili

Mettiamo un ostacolo X:

######################
#####.............####
###................###
##.....X.....XXX....##
#......X.......X.....#
#...X.XX.............#
#...X......@.........#
#...X..........X.....#
#...XXXXXX...........#
##..................##
###....X...........###
#####.............####
######################

Hai un elenco della X che si trova all'interno dell'area di visualizzazione, quindi contrassegni come nascoste tutte le tessere che si trovano dietro ciascuno di questi ostacoli: quando un ostacolo viene contrassegnato come nascosto, lo rimuovi dall'elenco.

######################
#####.............####
###................###
##.....X.....XXX....##
#......X.......X.....#
#...X.XX.............#
#...X......@.........#
#...X..........X.....#
#...XXXXX*...........#
##......##..........##
###....*#..........###
#####.###.........####
######################

Nell'esempio sopra puoi vedere l'ombra proiettata dall'estrema destra della parete inferiore e come questa ombra elimina l'ostacolo nascosto dall'elenco dell'ostacolo che devi controllare (X deve controllare; * controllato).

Se riesci a ordinare l'elenco usando un partiton binario in modo da controllare prima la cosest X, potresti accelerare leggermente il tuo controllo.

Puoi usare una sorta di algoritmo "Battaglie navali" per controllare il blocco di X in una volta (sostanzialmente alla ricerca di una X adiacente che è in una direzione che può allargare il cono d'ombra)

[MODIFICARE]

Sono necessari due raggi per proiettare correttamente un'ombra e, poiché una piastrella è rettangolare, si possono fare molte ipotesi usando le simmetrie disponibili.

Le coordinate del raggio possono essere calcolate usando un semplice partizionamento dello spazio attorno alla tessera ostacolo:

Esempio di partizionamento dello spazio

Ogni area rettangolare costituisce una scelta su quale angolo della piastrella dovrebbe essere preso come bordo del cono d'ombra.

Questo ragionamento può essere spinto ulteriormente per collegare più tessere adiacenti e consentire loro di lanciare un singolo cono più largo come segue.

Il primo passo è garantire che non vi siano ostacoli verso la direzione dell'osservatore, in tal caso si considera invece l'ostacolo più vicino:

scegli l'ostacolo più vicino

Se la tessera gialla è un ostacolo, quella tessera diventa la nuova tessera rossa.

Ora consideriamo il bordo del cono superiore:

tessere candidate

Le tessere blu sono tutte possibili candidate per allargare il cono d'ombra: se almeno una di esse è un ostacolo, il raggio può essere spostato usando lo spazio che divide intorno a quella tessera come visto in precedenza.

La tessera verde è candidata solo se l'osservatore si trova sopra la linea arancione che segue:

controllo esteso

Lo stesso vale per l'altro raggio e per le altre posizioni dell'osservatore rispetto all'ostacolo rosso.

L'idea alla base è quella di coprire quanta più area possibile per ogni lancio del cono e di abbreviare il più rapidamente possibile l'elenco degli ostacoli da controllare.


Approccio interessante e probabilmente un'idea migliore a causa della sua natura sottrattiva. Dopo aver letto questo, probabilmente lo implementerei anche in questo modo.
David Gouveia,

Posso prevedere problemi in situazioni come questa . Giocatore in giallo, ostacoli in blu e viola. Il giocatore dovrebbe essere in grado di vedere l'ostacolo viola (come mostra il raggio verde). Ma il raggio di ombra rossa che passa attraverso l'ostacolo blu rifiuta la piastrella viola. Ma immagino che la versione della linea di vista abbia il potenziale per avere problemi più grandi di così.
David Gouveia,

Questo problema deriva dalla definizione di "nascosto": quando un raggio interseca una piastrella (quasi) non lo coprirà mai completamente. Lo stesso problema viene risolto con l'aliasing durante il rendering dei segmenti di linea. Personalmente penso che una tessera sia nascosta quando la maggior parte è coperta, si può definire che è nascosta è completamente coperta, potresti scoprire se espone un lato che potenzialmente potrebbe allargare il cono d'ombra ... Comunque, potresti eliminare solo i blocchi completamente coperti.
FxIII

@DavidGouveia - quali problemi maggiori?
Rogach,

@DavidGouveia - Ho già provato ad avvicinarmi con "coni" d'ombra, ed è stato molto inefficiente. Per quanto riguarda la precisione dei raggi di visibilità, ~ 5500 raggi sono sufficienti per vedere il muro 20 tessere in ogni direzione se ci si trova direttamente vicino ad esso, e la distanza in cui è visibile solo una singola tessera è molto più. E per quanto riguarda la mancanza di alcune tessere a grande distanza - beh, non tutti hanno una vista perfetta, eh?
Rogach,

8

Il problema che stai cercando di risolvere è talvolta chiamato Field of View, abbreviato in FOV. Come hai citato roguelike come esempi, dovresti dare un'occhiata a ciò che il wiki di RogueBasin ha da dire sull'argomento (ci sono anche collegamenti alle implementazioni): http://www.roguebasin.com/index.php?title=Field_of_Vision

Esistono diversi algoritmi diversi con pro e contro diversi: un confronto molto utile è disponibile anche su RogueBasin: http://www.roguebasin.com/index.php?title=Comparative_study_of_field_of_view_algorithms_for_2D_grid_based_worlds


Riepilogo davvero buono e completo!
Rogach,

Quel sito web è un'ottima risorsa, grazie per aver condiviso quel link. Contiene anche una descrizione incredibilmente comprensibile di come funziona A * pathfinding :-)
uliwitness

Il link in risposta ora va alla home page del sito - roguebasin.com/index.php?title=Category:FOV sembra essere una corrispondenza ragionevole.
fadden,


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.