Un modo per archiviare potenzialmente infiniti dati cartografici 2D?


29

Ho un platform 2D che attualmente può gestire blocchi con 100 per 100 tessere, con le coordinate del blocco sono memorizzate come long, quindi questo è l'unico limite delle mappe (maxlong * maxlong). Tutte le posizioni delle entità ecc. Ecc. Sono rilevanti per la parte e quindi non vi sono limiti.

Il problema che sto riscontrando è come archiviare e accedere a questi blocchi senza avere migliaia di file. Qualche idea per un formato di archivio HD preferibilmente rapido ea basso costo che non debba aprire tutto in una volta?


2
Alcune strutture di dati che è possibile esaminare per maggiore ispirazione sono matrici sparse e tabelle di pagine (multilivello) .
Andrew Russell,

Bassa priorità: potresti chiarire se il tipo di dati "lungo" è a 32 o 64 bit?
Randolf Richardson,

2
@Randolf dato che questo è C #, presumibilmente intende il C # longche è 64 bit (quindi è maxlong Int64.MaxValue).
Andrew Russell,

Notch ha alcune cose interessanti da dire sulle infinite mappe di Minecraft nel suo blog qui: notch.tumblr.com/post/3746989361/terrain-generation-part-1
dlras2

Risposte:


17

Crea un formato mappa personalizzato per il tuo gioco. È più facile di quanto tu possa pensare. Usa semplicemente la classe BinaryWriter. Per prima cosa scrivi l'intestazione tra qualche ints o uint. Informazioni da includere nell'intestazione:

  • La stringa magica / numero magico del tuo formato di file.
  • L'inizio / fine / dimensione dei blocchi descritti in questo file

e anche (e qui arriva la parte critica delle prestazioni

  • ints che descrivono la posizione iniziale all'interno del file. Quindi non devi cercare pezzi specifici.

Con il metodo sopra puoi (e dovresti) creare un indice del contenuto dei tuoi file, contenente una sorta di descrizione (un nome specificato dall'utente per la regione / blocco, o solo le coordinate) e come secondo valore la posizione nel file.

Quindi, quando vuoi caricare un blocco specifico, dovrai solo cercare all'interno dell'indice. Quando hai ottenuto la posizione, imposta semplicemente fileStream.Position = PositionOfChunkFromIndex e puoi caricarlo.

Riguarda il design del fileformat con l'intestazione che descrive il contenuto del file in modo più efficiente.

Basta salvare i file con un'estensione personalizzata che hai creato e il gioco è fatto.

BONUS: aggiungi la compressione BZip2 a specifiche aree del file / l'intero contenuto (non l'intestazione !!), quindi puoi decomprimere blocchi specifici dal file, per un ingombro di memoria molto ridotto.


12
Vale la pena sottolineare che, se stai per modificare questo file al volo, vorrai un'intestazione / indice di dimensioni fisse o esterne, in modo tale da poter aggiungere blocchi al file senza dover riscrivere il intero file (a causa della modifica degli offset).
Andrew Russell,

A quel punto, non stai semplicemente implementando un database flatfile?
Ape-inago,

13

Ho riscontrato un problema simile e ho deciso di creare la mia struttura per gestire i dati. È liberamente basato su un quadrifoglio, ma ha un'espandibilità infinita (almeno grande quanto un Int) in tutte le direzioni. È stato progettato per gestire i dati basati sulla griglia che si sono espansi da un punto centrale, proprio come Minecraft ora fa. È efficiente nello spazio in memoria e molto veloce.

È possibile specificare una magnitudine minima per ciascun nodo (una magnitudine di 7 sarebbe 128x128) e una volta che un nodo ha una percentuale specifica dei suoi nodi secondari popolata, si appiattisce automaticamente in un array bidimensionale. Ciò significa che una porzione molto densamente popolata (ad esempio un continente completamente esplorato) avrà le prestazioni di un array (molto veloce) ma una porzione scarsamente popolata (ad esempio, un litorale che qualcuno ha vagato su e giù ma non ha esplorato nell'entroterra) avere buone prestazioni e un basso utilizzo della memoria.

Il mio codice può essere trovato qui . Il codice è completo, testato (test unitari e di carico) e abbastanza ottimizzato. I meccanismi interni non sono ancora ben documentati, tuttavia, ma tutti i metodi pubblici sono quindi dovrebbe essere utilizzabile. Se qualcuno decide di provarlo, non esitare a contattarmi per domande o commenti.

Non l'ho ancora usato per archiviare i dati in un file, ma è un problema interessante e potrei affrontarlo in seguito.


Quindi questo è fondamentalmente un albero espandibile, giusto? Cosa mi sto perdendo?
Kaao,

2
Il più grande miglioramento rispetto a un albero espandibile è che "appiattisce" alcuni nodi dell'albero che sono pesantemente popolati (default 70%) in array 2D, piuttosto che mantenerli strutturati come alberi. Questo ti dà la velocità di una ricerca di array, senza le dimensioni (infinite) di un array infinito.
dlras2,

Entrambi i nodi foglia e interno, giusto? Un'idea interessante, potrebbe dare buoni risultati, ci proverò se mai ne avrò bisogno. A proposito, +1 per aver dato il codice e la risposta rapida! Oh, e anche i test unitari, purtroppo non lo faccio mai nei miei progetti personali :)
kaoD

Non eseguiamo test di unità sul mio lavoro, quindi purtroppo è il mio modo di ribellarmi. Ho creato un'app demo per mostrare che si popola e si appiattisce, quindi se riesco a ripulirlo nei prossimi giorni quindi è presentabile, lo posterò anche qui. Ha molto più senso quando lo vedi.
dlras2,

1
L'ho perso di vista, scusa! Vorrei ancora ripulirlo, ma sto lentamente rielaborando parte del codice tra classe e compiti a casa, quindi non sarà per un po '. Per ora, la vecchia demo non carina è qui: j.mp/qIwKYt Per non carina, intendo in parte che richiede molte spiegazioni, quindi non dimenticare di leggere il file README e sentiti libero di porre domande qui o Via Posta Elettronica.
dlras2,

3

Invece potresti usare un database - PostgreSQL ha alcune speciali capacità di indicizzazione ottimizzate per questo tipo di dati che si trova nelle coordinate X e Y. È inoltre possibile specificare che i dati restituiti si trovano entro un certo raggio anziché in un'area quadrata o di forma oblunga.

  PostgreSQL (gratuito e open source)
  http://www.postgresql.org/

Esistono anche altri database e per il lato client potresti trovare alcuni tipi più adatti a questo poiché possono essere eseguiti autonomamente (avviati dall'applicazione client di gioco) o possono essere inclusi come parte di una libreria di codici che puoi "semplicemente usare". Il vantaggio è che non è necessario progettare uno schema di indicizzazione perché la maggior parte dei motori di database SQL lo fa già abbastanza bene.

Un vantaggio con l'approccio del database è che puoi ridurre le dimensioni dei tuoi blocchi (o eliminare completamente i blocchi e utilizzare direttamente i riquadri, ma l'uso di almeno piccoli blocchi / gruppi di più riquadri può essere più efficiente a seconda del progetto), e quindi utilizzare la query SQL per portare in un'area più ampia di quanto sia visualizzabile. Pre-caricando per sovrapporre aree non visibili nelle vicinanze, le tessere possono essere preparate prima che il giocatore muova il proprio personaggio, risultando in un'esperienza di gioco migliore (si spera più liscia).

Ho notato che alcuni giochi mantengono una "cache" dei dati della mappa sul disco rigido locale dopo averli ottenuti la prima volta (questo è senza dubbio per ridurre l'I / O di rete), come Ashen Empires:

  Ashen Empires (gioco gratuito, bella implementazione 2D)
  http://www.ashenempires.com/

Anche tenere traccia degli "ultimi aggiornamenti" dei timestamp con ogni blocco / riquadro sarà utile poiché, per i casi in cui sono disponibili dati memorizzati localmente, la query SQL potrebbe includere una clausola aggiuntiva "WHERE timestamp_column> $ local_timestamp" in modo da ottenere solo blocchi / riquadri aggiornati scaricati (due vantaggi del risparmio di larghezza di banda come questo sono minori costi di connettività e minore ritardo per i tuoi giocatori, che diventerà più evidente quando il tuo gioco diventerà popolare).

Una schermata di Ashen Empires (alcuni personaggi sono in una banca locale, e dall'aspetto di quelle ossa sul pavimento sembra che alcuni scheletri di mostri devono aver vagato dentro e probabilmente sono stati massacrati dalle guardie della città locale):

inserisci qui la descrizione dell'immagine


2

Non immagazzinarli e accedervi, memorizzare solo i semi casuali necessari e le modifiche del giocatore sulla mappa. Quindi genera le parti richieste in fase di esecuzione (esegui l'algoritmo di generazione, quindi applica le modifiche del giocatore). Con una procedura di generazione corretta e coerente, la mappa risultante sarà sempre la stessa per lo stesso seme iniziale.

Teoricamente puoi fare una mappa letteralmente infinita che salverà in un file molto piccolo in questo modo.


@Josh Petrie grazie per le significative e significative correzioni linguistiche / stilistiche al mio post. peccato che non possa votare una modifica MrGreen
codice sh

1

C'è un modo per dividere i blocchi (una sorta di "subcontinenti / paesi" nel tuo mondo)? Quindi forse potresti avere un qualche tipo di file indice che ti consente di trovare rapidamente quale sotto-file / parte di file più grande devi caricare per avere un pezzo in memoria ...


c'è sempre un modo per partizionare i pezzi. SEMPRE. che siano visibili al giocatore / rilevanti per il resto del sistema o meno, c'è sempre un modo per partizionare i dati del mondo in blocchi, di solito in diversi modi.
codice sh

0

Potresti prendere l'idea da Minecraft. Inizialmente avevano un file per blocco. Ora usano il formato MCRegion, che raggruppa i blocchi in aree 32x32 e ne memorizza uno per file.

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.