La mappa con 20 milioni di tessere rende il gioco esaurito la memoria, come posso evitarlo?


11

Durante il caricamento di mappe extra-grandi, il metodo di caricamento genera un'eccezione di memoria in cui viene creata una nuova istanza del riquadro della mappa. Vorrei che l'intera mappa fosse elaborata almeno sull'app server (e sul client, se possibile). Come devo risolvere questo problema?

UPD: la domanda qui è come impedire il crash del gioco quando c'è ancora memoria libera da usare. Per quanto riguarda la suddivisione della mappa in blocchi, è un buon approccio, ma non quello che voglio nel mio caso.

UPD2: All'inizio sono andato e ho assegnato una trama a ogni nuova istanza della classe tile ed è ciò che ha richiesto così tanta memoria (anche il tempo di caricamento). Ora occupa circa quattro volte meno spazio. Grazie a tutti, ora posso eseguire enormi mappe senza pensare di romperle in pezzi ancora.

UPD3: Dopo aver riscritto il codice per vedere se le matrici delle proprietà delle tessere funzionano più velocemente o consumano meno memoria (rispetto a quelle proprietà nelle rispettive tessere come proprietà degli oggetti), ho scoperto che non solo mi ci è voluto molto tempo per provarlo, ma non ha apportato alcun miglioramento delle prestazioni e ha reso il gioco terribilmente difficile da eseguire il debug.


8
Carica solo le aree intorno ai giocatori.
MichaelHouse

4
20 milioni di tessere a 4 byte per tessera sono solo circa 80 MB, quindi sembra che le tessere non siano così piccole. Non possiamo magicamente darti più memoria, quindi dobbiamo capire come ridurre i tuoi dati. Quindi, mostraci cosa c'è in queste tessere.
Kylotan,

Cosa fa il metodo di caricamento? Codice postale, usi la pipeline di contenuti? Carica trame in memoria o solo metadati? Quali metadati? I tipi di contenuto XNA fanno generalmente riferimento a elementi DirectX non gestiti, quindi gli oggetti gestiti sono molto piccoli, ma sul lato non gestito potrebbero esserci un sacco di cose che non vedi a meno che tu non stia eseguendo anche un profiler DirectX. stackoverflow.com/questions/3050188/...
Oskar Duveborn

2
Se il sistema genera un'eccezione di memoria esaurita, allora non c'è abbastanza memoria libera da usare, nonostante ciò che potresti pensare. Non ci sarà alcuna linea di codice segreta che possiamo darti per abilitare memoria aggiuntiva. Il contenuto di ogni riquadro e il modo in cui li stai assegnando è importante.
Kylotan,

3
Cosa ti fa pensare di avere memoria libera? Stai eseguendo un processo a 32 bit all'interno di un sistema operativo a 64 bit?
Dalin Seivewright,

Risposte:


58

l'app si arresta in modo anomalo quando raggiunge 1,5 GB.

Questo suggerisce fortemente che non stai rappresentando correttamente le tue tessere, poiché ciò significherebbe che ogni tessera ha una dimensione di ~ 80 byte.

Quello che devi capire è che deve esserci una separazione tra il concetto di gioco di una tessera e la tessera visiva che l'utente vede. Questi due concetti non sono la stessa cosa .

Prendi Terraria per esempio. Il mondo Terraria più piccolo occupa 4200x1200 tessere, ovvero 5 milioni di tessere. Ora, quanta memoria ci vuole per rappresentare quel mondo?

Bene, ogni piastrella ha uno strato in primo piano, uno strato di sfondo (le pareti dello sfondo), uno "strato di filo" dove vanno i fili e uno "strato di mobili" dove vanno gli oggetti di arredamento. Quanta memoria occupa ogni tessera? Ancora una volta, stiamo solo parlando concettualmente, non visivamente.

Una tessera in primo piano può essere facilmente memorizzata in un corto senza segno. Non ci sono più di 65536 tipi di riquadri in primo piano, quindi non ha senso usare più memoria di così. I riquadri di sfondo potrebbero trovarsi facilmente in un byte senza segno, poiché esistono meno di 256 tipi diversi di riquadri di sfondo. Lo strato di filo è puramente binario: o una piastrella ha un filo o no. Quindi questo è un bit per tessera. E lo strato di mobili potrebbe essere di nuovo un byte senza segno, a seconda di quanti possibili diversi mobili ci sono.

Dimensione totale della memoria per riquadro: 2 byte + 1 byte + 1 bit + 1 byte: 4 byte + 1 bit. Pertanto, la dimensione totale per una piccola mappa Terraria è 20790000 byte, o ~ 20 MB. (nota: questi calcoli si basano su Terraria 1.1. Da allora il gioco si è molto ampliato, ma anche la moderna Terraria potrebbe contenere 8 byte per posizione delle tessere o ~ 40 MB. Ancora abbastanza tollerabile).

Non si dovrebbe mai avere questa rappresentazione memorizzata come array di classi C #. Dovrebbero essere matrici di numeri interi o qualcosa di simile. Anche AC # struct avrebbe funzionato.

Ora, quando arriva il momento di disegnare parte di una mappa (notare l'enfasi), Terraria deve convertire queste tessere concettuali in tessere reali . Ogni tessera deve effettivamente scegliere un'immagine in primo piano, un'immagine di sfondo, un'immagine del mobile opzionale e avere un'immagine a filo. È qui che entra in gioco XNA con i suoi vari fogli sprite e così via.

Quello che devi fare è convertire la parte visibile della tua mappa concettuale in tessere di sprite XNA reali. Non dovresti provare a convertire l' intera cosa in una volta . Ogni riquadro che memorizzi dovrebbe essere solo un indice che dice "Sono un tipo di riquadro X", dove X è un numero intero. Usa quell'indice intero per recuperare quale sprite usi per visualizzarlo. E usi i fogli sprite di XNA per renderlo più veloce del semplice disegno di singoli quadricipiti.

Ora la regione visibile delle tessere deve essere suddivisa in vari blocchi, in modo da non creare costantemente fogli sprite ogni volta che la telecamera si sposta. Quindi potresti avere blocchi 64x64 del mondo come fogli sprite. Qualunque pezzo 64x64 del mondo sia visibile dalla posizione attuale della telecamera del giocatore sono i pezzi che disegni. Altri pezzi non hanno nemmeno fogli di sprite; se un pezzo cade dallo schermo, lo butti fuori (nota: non lo elimini davvero; lo tieni attorno e lo rispetti per un nuovo pezzo che potrebbe diventare visibile in seguito).

Vorrei che l'intera mappa fosse elaborata almeno sull'app server (e sul client, se possibile).

Il tuo server non ha bisogno di conoscere o preoccuparsi della rappresentazione visiva dei riquadri. Tutto ciò che deve preoccuparsi è la rappresentazione concettuale. L'utente aggiunge un riquadro qui, quindi cambia l'indice del riquadro.


4
+1 Risposta eccellente. Deve essere votato più del mio.
MichaelHouse

22

Dividi il terreno in regioni o blocchi. Quindi carica solo i pezzi che sono visibili ai giocatori e scarica quelli che non lo sono. Puoi immaginarlo come un nastro trasportatore, in cui carichi pezzi da un lato e li scarichi dall'altro mentre il giocatore si muove. Stare sempre davanti al giocatore.

Puoi anche usare trucchi come istanza. Dove se tutte le tessere di un tipo hanno solo un'istanza in memoria e vengono disegnate più volte in tutte le posizioni in cui è necessario.

Puoi trovare altre idee come questa qui:

Come hanno fatto: milioni di piastrelle in Terraria

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


Il problema è che tale approccio è buono per l'app client, ma non altrettanto per il server. Quando alcune tessere nel mondo si spengono e inizia una reazione a catena (e ciò accade molto) l'intera mappa dovrebbe essere in memoria per questo ed è un problema quando si esaurisce la memoria.
user1306322

6
Cosa contiene ciascuna tessera? Quanta memoria viene utilizzata per riquadro? Anche a 10 byte ciascuno sei solo a 190 megabyte. Il server non dovrebbe caricare la trama o qualsiasi altra informazione non necessaria. Se hai bisogno di una simulazione per 20 milioni di tessere, devi rimuovere il più possibile da quelle tessere per formare un set di simulazione che contenga solo le informazioni richieste per le tue reazioni a catena.
MichaelHouse

5

La risposta di Byte56 è buona e ho iniziato a scriverlo come commento, ma è diventato troppo lungo e contiene alcuni suggerimenti che potrebbero essere più utili in una risposta.

L'approccio è assolutamente valido sia per il server che per un client. In effetti è probabilmente più appropriato, dato che al server verrà chiesto di occuparsi di molte più aree di quanto non faccia un client. Il server ha un'idea diversa su ciò che deve essere caricato rispetto a un client (si preoccupa per tutte le regioni a cui interessano tutti i client connessi), ma è ugualmente in grado di gestire un set di regioni funzionante.

Anche se potresti adattare una mappa così gigantesca (e molto probabilmente non puoi) in memoria, non vuoi farlo . Esiste un punto assolutamente zero nel caricamento dei dati che non è necessario utilizzare immediatamente, è inefficiente e lento. Anche se potessi adattarlo alla memoria, molto probabilmente non saresti in grado di elaborare tutto in un ragionevole intervallo di tempo.

Ho il sospetto che la tua obiezione al fatto che alcune regioni vengano scaricate sia perché l'aggiornamento mondiale sta ripetendo tutte le tessere e le sta elaborando? Questo è certamente valido, ma non preclude il caricamento di determinate parti. Invece, quando viene caricata una regione, applica tutti gli aggiornamenti che sono stati persi, per aggiornarla. Anche questo è molto più efficiente dal punto di vista dell'elaborazione (concentrando un grande sforzo su una piccola area di memoria). Se ci sono effetti di elaborazione che potrebbero attraversare i confini delle regioni (ad esempio un flusso di lava o un flusso d'acqua che trasporterà in un'altra regione), quindi unire insieme queste due regioni in modo che quando una è caricata, così sia l'altra. Idealmente, tali situazioni sarebbero ridotte al minimo, altrimenti ti ritroverai rapidamente nel caso "tutte le regioni devono essere caricate in ogni momento".


3

Un modo efficiente che ho usato in un gioco XNA era:

  • usa gli spritesheets, non un'immagine per ogni riquadro / oggetto, e carica semplicemente quello che ti servirà su quella mappa;
  • dividi la mappa in parti più piccole, ad esempio mappe di blocchi di 500 x 200 tessere se la tua mappa è 4 volte questa dimensione o qualcosa che puoi strappare;
  • carica questo pezzo in memoria e dipingi solo le parti visibili della mappa (diciamo, le tessere visibili più 1 tessera per ogni direzione in cui il giocatore si sta muovendo) in un buffer fuori schermo;
  • cancella il viewport e copia i pixel dal buffer (questo si chiama double buffer e leviga il movimento);
  • quando il tuo personaggio si sposta, segui i boud della mappa e carica il prossimo pezzo quando si avvicina ad esso e aggiungi a quello corrente;
  • una volta che il giocatore è completamente in questa nuova mappa, puoi scaricare l'ultimo.

Puoi anche usare una sola grande mappa in questo modo se non è così grande e usare la logica presentata.

Ovviamente le dimensioni delle trame devono essere bilanciate e la risoluzione paga un ottimo prezzo in questo. Prova a lavorare a una risoluzione inferiore e, se necessario, crea un pacchetto ad alta risoluzione da caricare se un'impostazione di configurazione è impostata su.

Dovrai ripetere ogni volta solo le parti rilevanti di questa mappa e renderizzare solo ciò che è necessario. In questo modo migliorerai molto le prestazioni.

Un server può elaborare una mappa enorme più velocemente perché non deve eseguire il rendering (una delle operazioni più lente) del gioco, quindi la logica può essere l'uso di blocchi più grandi o persino dell'intera mappa, ma elaborare solo la logica intorno ai giocatori, come in una vista 2 volte l'area del giocatore intorno al giocatore.

Un consiglio qui è che non sono affatto un esperto in sviluppo di giochi e il mio gioco è stato creato per il progetto finale della mia laurea (che ho ottenuto il miglior punteggio), quindi potrei non essere così giusto in ogni punto, ma ho ha fatto ricerche sul Web e su siti come http://www.gamasutra.com e il sito dei creatori di XNA creators.xna.com (in questo momento http://create.msdn.com ) per raccogliere conoscenze e abilità e ha funzionato bene per me .


2

O

  • acquista più memoria
  • rappresentare le strutture dei dati in modo più compatto
  • non conservare tutti i dati in memoria contemporaneamente.

Ho 8 GB di RAM su Win7 64 bit e l'app si arresta in modo anomalo quando raggiunge 1,5 GB. Quindi non ha davvero senso acquistare più RAM a meno che non mi manchi qualcosa.
user1306322

1
@ user1306322: se hai 20 milioni di riquadri e occupano circa ~ 1,5 GB di memoria, ciò significa che i riquadri sono circa 80 byte per riquadro . Cosa memorizzi esattamente in queste cose?
Nicol Bolas,

@NicolBolas come ho detto, qualche ints, byte e un texture2d. Inoltre, non tutti richiedono 1,5 GB, l'app si arresta in modo anomalo quando raggiunge questo costo di memoria.
user1306322

2
@ user1306322: è ancora molto più di quanto deve essere. Quanto è grande un texture2d? Ogni tessera ne ha una unica o condividono le trame? Inoltre, dove l' hai detto? Di certo non l'hai messo nella tua domanda, che è dove dovrebbero essere le informazioni. Spiega meglio le tue circostanze specifiche, per favore.
Nicol Bolas,

@ user1306322: Francamente, non capisco perché la mia risposta sia stata sottoposta a votazione negativa. Ho elencato tre rimedi per situazioni di memoria insufficiente - ovviamente, ciò non significa che tutti si applichino necessariamente. Ma penso ancora che siano corretti. Avrei probabilmente dovuto scrivere "estendi la tua memoria" invece di "comprare" per includere il caso delle macchine virtuali. Sono stato retrocesso perché la mia risposta è stata concisa anziché dettagliata? Penso che i punti siano abbastanza semplici da essere compresi senza ulteriori spiegazioni ?!
Thomas,

1

la domanda qui è come impedire il crash del gioco quando c'è ancora memoria libera da usare. Per quanto riguarda la suddivisione della mappa in blocchi, è un buon approccio, ma non quello che voglio nel mio caso.

In risposta al tuo aggiornamento, non puoi allocare array Int32.MaxValuedi dimensioni maggiori. Devi dividerlo in pezzi. Potresti anche incapsularlo in una classe wrapper che espone una facciata simile ad un array:

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.