'Suddivisione in zone' di aree su una grande mappa di tessere, così come sotterranei


10

Il mio gioco ha una mappa come quella di Minecraft, nel modo in cui è pseudoinfinito e generato casualmente. E grande. Supponiamo che l'utente abbia esplorato una zona 1000x1000 (qui 2D), quindi sono 1.000.000 di tessere.

Ovviamente non sarò in grado di archiviare tutto in memoria. Né voglio solo ignorare tutto da una tessera da 10 o qualunque raggio - entrambi non aggiorneranno nulla (tutti gli NPC, forse tessere reattive) e dovrei lavorare con posizioni scomode come 1382,12918.

Quindi, se dovessi dividerlo in blocchi o zone o qualsiasi altra cosa delle tessere 64x64, dovrei memorizzare ogni piastrella e la posizione dell'oggetto come:

Pezzo a, b. Posizione x, y.

E se volessi sotterranei e simili nella mia mappa? Quindi una singola tessera che porta forse a un sotterraneo di 40 piani, ciascuno con un'area di 40x40. Non riesco esattamente a memorizzarli nella stessa mappa.

E c'è il lato della memoria; quanto potrei potenzialmente memorizzare in memoria contemporaneamente ragionevolmente? Potrei fare tessere per ID abbastanza facilmente e avere il normale array 2D lì. O ancora, esiste una soluzione più efficace che avere un file di dati di tipi di tile ? Quindi per un 64x64 ... sarà solo 20K o altro. Vorrei caricare il più possibile l'ambiente circostante per l'aggiornamento più realistico. Ma non so che tipo di limiti posso raggiungere senza diventare un hogging della memoria.

Risposte:


9

Anche se sono d'accordo con il sentimento: "Non preoccuparti se non è un problema provato", penso che valga la pena di pensarci presto: adattarsi a una soluzione retro è molto più doloroso. E sì, solo l'aggiornamento delle tessere "vicine" è la strada da percorrere. Ma l'archiviazione e l'indirizzamento degli oggetti nel tuo mondo di gioco in modo efficiente è molto importante per motivi di prestazioni.

Quello a cui stai davvero pensando qui è un set di dati scarso: qualcosa in cui i potenziali indici sono grandi (o illimitati), ma solo una piccola parte viene effettivamente utilizzata. Il punto chiave è che non sai esattamente quale proporzione verrà utilizzata.

La soluzione standard a un problema con un set di dati scarso consiste nel separare l'indice / indirizzabilità dall'effettiva memorizzazione dei dati. Quindi, se l'oggetto piastrella è costoso, conservalo in una forma compatta (ad esempio un array piatto). Ma consenti che sia indicizzato attraverso un oggetto più economico. Nella sua forma più semplice, questa può essere una matrice 2D (o 3D) che puoi facilmente indicizzare per coordinata, ma ogni elemento nella matrice è semplicemente un indice. Quindi utilizzare tale indice per cercare il contenuto effettivo del riquadro in un array separato e compatto. Se il contenuto del riquadro non esiste ancora, aggiungerli alla fine dell'array e memorizzare l'indice nella matrice 3D.

La soluzione diventa più complessa se si desidera supportare la cancellazione dei contenuti (in quanto porta alla frammentazione della matrice dei contenuti) e se i contenuti delle tessere sono economici, il peso aggiuntivo dell'indice (indici a 32 o 64 bit) probabilmente travolgerà i risparmi dal non memorizzare ogni singola potenziale piastrella. È anche una ricerca extra, che danneggerà le prestazioni della cache.

È possibile ottenere un'efficienza di archiviazione ancora maggiore introducendo livelli aggiuntivi di indiretta. Supponiamo che organizzi le tue tessere in blocchi e che i blocchi abbiano una granularità di 64x64x64. Data una tessera a 125, 1, 132, sai che appartiene a blocco (1,0,2). Quindi hai un mondo, che consiste in un array di blocchi compatto e una matrice di indici di blocchi (-1 se il blocco non esiste). Il contenuto di ogni blocco (se presente) è una matrice 64x64x64 di indici di tessera (-1 se la tessera non esiste ancora) e una matrice compatta di tessere usate. In questo modo, non si bruciano enormi quantità di indici di tessere per blocchi che non vengono mai utilizzati. Seguendo questo tipo di approccio e selezionando numeri sensibili per la granularità di pezzi, puoi scalare il tuo universo in modo massiccio e tenere sotto controllo l'utilizzo della memoria. In realtà, se rendi i tuoi pezzi 32x32x32,

Puoi anche fare trucchi subdoli, come usare il bit di alto ordine del tuo pezzo o indici di piastrelle per significare qualcosa di speciale. Quindi se una voce nella matrice di tessere ha il bit più alto impostato, allora i 31 bit più bassi non significano un indice di piastrella, invece significano un "indice di curvatura" o qualcosa di simile, e puoi cercarlo in un elenco gestito separatamente per scoprire le coordinate a cui conduce.


Avvertirei chiunque incontrerà questa domanda in futuro: anche se nulla in questo post è errato, è onestamente orribile per un gioco come Minecraft, che ha un mondo non scarso. (E MrCranky dice altrettanto.) Il tuo mondo ne trae beneficio solo se ha qualche cosa e molte cose , non poche cose e molti vuoti .

1
Sono pienamente d'accordo sul fatto che l'overhead coinvolto nel mantenimento di una soluzione di archiviazione di set di dati sparsi sia piuttosto elevato, quindi ci proveresti solo se il saldo fosse chiaramente inclinato verso la scarsità. I fattori chiave qui sono: costo-di-elemento-dati e probabilità-che-elemento-dati-non viene utilizzato. Minecraft ha un enorme, enorme set di dati potenziali (un numero elevato di tessere in ogni direzione). La proporzione effettiva del potenziale mondo di gioco utilizzato è minuscola. Anche se ne rivendichi ampie parti mentre ti muovi in ​​tutto il mondo, è ancora una piccola frazione dei potenziali elementi di dati.
MrCranky,

1
Ciò che rende diverso Minecraft è che il costo per unità è piccolo - forse è un singolo byte di memoria? Un valore indica "niente lì", gli altri sono tipi di blocco e puoi facilmente inserirlo in un byte. Quindi non avresti un indice su un singolo blocco, è semplicemente matto, l'indice è molto più grande della semplice memorizzazione del valore del blocco reale. Ma le regole dei set di dati sparsi funzionano ancora ai livelli più alti. Ogni blocco (ad es. 64x64x64) è costoso da archiviare (256 KB di blocchi), quindi se puoi utilizzare un singolo piccolo valore per indicare la sua assenza o il suo indirizzo se esiste, hai risparmiato spazio di archiviazione enorme.
MrCranky,

6

Perché non riesci a memorizzare un milione di tessere in memoria? Anche il mio telefono ha 256 MB di RAM; un milione di tessere vuote sarà cosa, 4-32 MB?


Sembrava un numero così grande da memorizzare. Inoltre, ci vorrà molto tempo per aggiornarli.
Il comunista Duck il

2
È molto più semplice ignorare una serie di riquadri per gli aggiornamenti piuttosto che gestire alcune soluzioni di paginazione del disco. Solo ... ignorali. Non aggiornare le tessere a più di X di distanza da un attore noto.

2
@TheCommunistDuck L'unica cosa che conta è la quantità totale di memoria utilizzata per memorizzare le tessere, non il numero di tessere.
Giustino,

1
Concordato con Kragen e Joe. Non preoccuparti di ciò che sembra molto: fai la matematica e risolvila. È improbabile che si tratti di un problema.
Kylotan,
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.