Design dell'intera mappa e design dell'array di piastrelle


11

Sto lavorando a un gioco di ruolo 2D, che presenterà le consuete mappe dei sotterranei / città (pre-generate).

Sto usando le tessere, che poi combinerò per creare le mappe. Il mio piano originale era quello di assemblare le piastrelle usando Photoshop, o qualche altro programma grafico, al fine di avere un'immagine più grande che potevo quindi usare come mappa.

Tuttavia, ho letto su diversi punti in cui le persone parlano di come hanno usato le matrici per costruire la loro mappa nel motore (in modo da dare una serie di x tessere al tuo motore e le assembla come una mappa). Riesco a capire come è fatto, ma sembra molto più complicato da implementare e non riesco a vedere ovvi vantaggi.

Qual è il metodo più comune e quali sono i vantaggi / gli svantaggi di ciascuno?


1
Il problema principale è la memoria. Una trama che riempie una volta uno schermo a 1080p è di circa 60mb. Quindi un'immagine usa un int per pixel, una tessera usa un int per (pixel / (tilesize * tilesize)). Quindi se le tessere sono 32,32 puoi rappresentare una mappa 1024 volte più grande usando le tessere contro un pixel. Anche i tempi di scambio delle trame danneggiano la memoria, ecc.
ClassicThunder,

Risposte:


19

Prima di tutto, lasciami dire che i giochi di ruolo 2D sono vicini e cari al mio cuore e lavorano con i vecchi motori DX7 VB6 MORPG (non ridere, è stato 8 anni fa, ora :-)) è ciò che mi ha interessato per lo sviluppo del gioco . Più recentemente, ho iniziato a convertire un gioco su cui ho lavorato in uno di quei motori per usare XNA.

Detto questo, la mia raccomandazione è di usare una struttura a tessere con strati per la tua mappa. Con qualsiasi API grafica che usi, avrai un limite alla dimensione delle trame che puoi caricare. Per non parlare dei limiti di memoria della trama della scheda grafica. Quindi, considerando questo, se vuoi massimizzare la dimensione delle tue mappe non solo minimizzando la quantità e la dimensione delle trame che carichi in memoria, ma anche diminuendo la dimensione delle tue risorse sul disco rigido dell'utente E i tempi di caricamento, sei sicuramente vorrai andare con le piastrelle.

Per quanto riguarda l'implementazione, sono andato nei dettagli su come l'ho gestito su alcune domande qui su GameDev.SE e sul mio blog (entrambi collegati di seguito), e non è esattamente quello che mi stai chiedendo, quindi mi limiterò a vai alle basi qui. Prenderò anche nota delle caratteristiche delle tessere che le rendono utili rispetto al caricamento di diverse immagini pre-renderizzate di grandi dimensioni. Se qualcosa non è chiaro, fammi sapere.

  1. La prima cosa che devi fare è creare un foglio di piastrelle. Questa è solo una grande immagine che contiene tutte le tessere allineate in una griglia. Questo (e forse uno in più a seconda del numero di tessere) sarà l'unica cosa che devi caricare. Solo 1 immagine! Puoi caricare 1 per mappa o uno con ogni tessera del gioco; qualunque organizzazione lavori per te.
  2. Successivamente, devi capire come puoi prendere quel "foglio" e tradurre ogni tessera in un numero. Questo è piuttosto semplice con qualche semplice matematica. Nota che la divisione qui è divisione intera , quindi le posizioni decimali vengono eliminate (o arrotondate per difetto, se preferisci). Come convertire le celle in coordinate e viceversa.
  3. OK, ora che hai suddiviso il foglio in una serie di celle (numeri), puoi prendere quei numeri e inserirli in qualsiasi contenitore ti piaccia. Per semplicità, puoi semplicemente usare un array 2D.

    int[,] mapTiles = new int[100,100]; //Map is 100x100 tiles without much overhead
  4. Quindi, vuoi disegnarli. Uno dei modi in cui puoi renderlo MOLTO più efficiente (a seconda delle dimensioni della mappa) è calcolare solo le celle che la videocamera sta attualmente visualizzando e scorrere quelle. Puoi farlo recuperando le coordinate dell'array di riquadri della mappa degli angoli superiore sinistro ( tl) e inferiore destro ( br) della videocamera . Quindi loop da tl.X to br.Xe, in un loop nidificato, da tl.Y to br.Yper disegnarli. Esempio di codice di seguito:

    for (int x = tl.X; x <= br.X;x++) {
        for (int y = tl.Y; y <= br.Y;y++) {
            //Assuming tileset setup from image
            Vector2 tilesetCoordinate = new Vector2((mapTiles[x,y] % 8) * 32,(mapTiles[x,y] / 8) * 32);
            //Draw 32x32 tile using tilesetCoordinate as the source x,y
        }
    }
  5. Montepremi! Queste sono le basi del motore delle piastrelle. Puoi vedere che è facile avere anche una mappa 1000x1000 con un sovraccarico eccessivo. Inoltre, se hai meno di 255 riquadri, puoi utilizzare un array di byte che riduce la memoria di 3 byte per cella. Se un byte è troppo piccolo, un ushort probabilmente sarebbe sufficiente per le tue esigenze.

Nota: ho tralasciato il concetto di coordinate del mondo (che è su cui si baserà la posizione della fotocamera) poiché, a mio avviso, non rientra nell'ambito di questa risposta. Puoi leggerlo qui su GameDev.SE.

Risorse per il mio Tile-Engine
Nota: tutti questi sono indirizzati a XNA, ma si applica praticamente a qualsiasi cosa: devi solo cambiare le chiamate di sorteggio.

  • La mia risposta a questa domanda delinea come gestisco le celle della mappa e la stratificazione nel mio gioco. (Vedi terzo link.)
  • La mia risposta a questa domanda spiega come archiviare i dati in un formato binario.
  • Questo è il primo post sul blog (bene, tecnicamente il secondo, ma il primo "tecnico") sullo sviluppo del gioco su cui stavo lavorando. L'intera serie contiene informazioni su cose come pixel shader, illuminazione, comportamenti delle piastrelle, movimento e tutte quelle cose divertenti. In realtà ho aggiornato il post per includere il contenuto della mia risposta al primo link che ho pubblicato sopra, quindi potresti volerlo leggere (potrei aver aggiunto delle cose). Se hai domande, puoi lasciare un commento lì o qui e sarei felice di aiutarti.

Altre risorse del Tile-Engine

  • Il tutorial sul motore delle tessere di questo sito mi ha fornito le basi che ho usato per creare le mie mappe.
  • In realtà non ho ancora visto questi tutorial video perché non ho avuto il tempo, ma probabilmente sono utili. :) Tuttavia, potrebbero essere obsoleti se si utilizza XNA.
  • Questo sito ha alcuni tutorial che (penso) si basano sui video sopra. Potrebbe valere la pena dare un'occhiata.

Waow, sono due volte più informazioni di quelle che ho fatto e praticamente rispondono a tutte le domande che non ho fatto, grazie, grazie :)
Cristol.GdM

@Mikalichov - Sono contento di poterti aiutare! :)
Richard Marskell - Drackir il

In genere con un array 2D si desidera utilizzare la prima dimensione come Y e la successiva come X. In memoria un array sarà sequenzialmente 0,0-i quindi 1,0-i. Se esegui cicli nidificati con X prima gallina, stai effettivamente saltando avanti e indietro in memoria invece di seguire un percorso sequenziale di lettura. Sempre per (y) quindi per (x).
Brett W,

@BrettW - Un punto valido. Grazie. Mi sono chiesto come sono archiviate le matrici 2D in .Net (ordine di riga maggiore. Ho imparato qualcosa di nuovo oggi! :-)). Tuttavia, dopo aver aggiornato il mio codice, mi sono reso conto che è esattamente lo stesso di quello che stai descrivendo, solo con le variabili cambiate. La differenza sta nell'ordine in cui le tessere vengono disegnate. Il mio attuale codice di esempio disegna dall'alto verso il basso, da sinistra a destra, mentre quello che descrivi trarrebbe da sinistra a destra, dall'alto verso il basso. Quindi, per semplicità, ho deciso di ripristinare l'originale. :-)
Richard Marskell - Drackir il

6

Entrambi i sistemi basati su piastrelle e un modello statico / sistema di trama possono essere usati per rappresentare un mondo e ognuno ha diversi punti di forza. Se uno è migliore dell'altro si riduce al modo in cui usi i pezzi e a ciò che funziona meglio per il tuo skillset e le tue esigenze.

Detto questo, entrambi i sistemi possono essere utilizzati separatamente o anche insieme.

Un sistema standard basato su piastrelle ha i seguenti punti di forza:

  • Matrice 2D semplice di piastrelle
  • Ogni piastrella ha un singolo materiale
    • Questo materiale può essere una singola trama, multiplo o miscelato dalle piastrelle circostanti
    • Può riutilizzare le trame per tutte le tessere di quel tipo, riducendo la creazione e la rielaborazione delle risorse
  • Ogni tessera può essere passabile o meno (rilevamento delle collisioni)
  • Facile da rendere e identificare parti della mappa
    • La posizione del personaggio e la posizione della mappa sono coordinate assolute
    • Semplice da scorrere tra riquadri pertinenti per la regione dello schermo

Lo svantaggio delle tessere è che è necessario creare un sistema per la creazione di questi dati basati sulle tessere. È possibile creare un'immagine che utilizza ogni pixel come riquadro, creando così l'array 2D da una trama. È inoltre possibile creare un formato proprietario. Usando i bitflags puoi archiviare un gran numero di dati per riquadro in uno spazio abbastanza piccolo.

Il motivo principale per cui molte persone fanno le tessere è perché consente loro di creare piccole risorse e riutilizzarle. Ciò consente di creare un'immagine molto più grande con pezzi più piccoli. Riduce le rilavorazioni perché non si sta modificando l'intera mappa del mondo per apportare una piccola modifica. Ad esempio, se volessi cambiare l'ombra di tutta l'erba. In un'immagine di grandi dimensioni dovresti ridipingere tutta l'erba. In un sistema di tessere è sufficiente aggiornare le tessere erba.

Tutto sommato ti ritroverai probabilmente a fare molte meno rielaborazioni con un sistema basato su tessere rispetto a una grande mappa grafica. Puoi finire per usare un sistema basato su piastrelle per collisione anche se non lo usi per la tua mappa del terreno. Solo perché usi una tessera internamente non significa che non puoi usare modelli per rappresentare gli oggetti dell'ambiente che possono usare più di una tessera per il loro spazio.

Dovresti fornire ulteriori dettagli sul tuo ambiente / motore / lingua per fornire esempi di codice.


Grazie! Sto lavorando con C # e sto cercando una versione simile (ma meno talentuosa) di Final Fantasy 6 (o fondamentalmente la maggior parte dei giochi di ruolo Square SNES), quindi piastrelle per pavimenti E strutture (ovvero case). La mia più grande preoccupazione è che non vedo come costruire l'array 2D senza passare ore a controllare "c'è un angolo di erba lì, poi tre orizzontali diritti, poi un angolo di casa, quindi ...", con tonnellate di possibili errori lungo il modo.
Cristol.GdM

Sembra che Richard ti abbia coperto in termini di un codice specifico. Personalmente userei un enum di tiletypes e userei bitflags per identificare il valore della piastrella. Ciò consente di memorizzare più di 32 valori in un numero intero medio. Sarai anche in grado di memorizzare più flag per riquadro. Quindi puoi dire Grass = true, Wall = true, Collision = true, ecc. Senza variabili separate. Quindi si assegnano semplicemente "temi" che determinano il foglio di piastrelle per la grafica per quella particolare area della mappa.
Brett W,

3

Disegnare la mappa: le tessere sono facili sul motore, perché in questo modo la mappa può essere rappresentata come una matrice gigante di indici di tessere. Disegna codice è solo un ciclo nidificato:

for i from min_x to max_x:
    for j from min_y to max_y:
        Draw(Tiles[i][j], getPosition(i,j))

Per le mappe di grandi dimensioni, un'immagine bitmap enorme per l'intera mappa potrebbe occupare molto spazio in memoria e potrebbe essere più grande di quella supportata da una scheda grafica per una singola dimensione di immagine. Ma non avrai problemi con i riquadri perché allochi memoria grafica solo una volta per ogni riquadro (o un totale, in modo ottimale, se sono tutti su un foglio)

È anche facile riutilizzare le informazioni sulla piastrella per es. Collisioni. per esempio, per ottenere la tessera terreno nell'angolo in alto a sinistra dello sprite del personaggio:

let x,y = character.position
let i,j = getTileIndex(x,y) // <- getTileIndex is the opposite function of getPosition
let tile = Tiles[i][j]

Supponiamo che sia necessario verificare la presenza di collisioni contro rocce / alberi, ecc. È possibile ottenere la tessera nella posizione (posizione carattere + dimensione sprite carattere + direzione corrente) e verificare se è contrassegnata come percorribile o non percorribile.

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.