Come usare Tile Bitmasking per affiancare automaticamente i layout del tuo livello

Realizzare un tileset visivamente accattivante e vario è un processo che richiede tempo, ma i risultati ne valgono spesso la pena. Tuttavia, anche dopo aver creato l'arte, devi comunque metterlo insieme nel tuo livello! 

Puoi posizionare ogni piastrella, una per una, a mano, oppure puoi automatizzare il processo usando bitmasking, quindi devi solo disegnare la forma del terreno.

Cosa è Tile Bitmasking?

Tile bitking è un metodo per selezionare automaticamente lo sprite appropriato da un tileset definito. Ciò ti consente di posizionare una tessera segnaposto generica ovunque desideri che appaia un particolare tipo di terreno invece di posizionare una selezione potenzialmente enorme di varie tessere. 

Guarda questo video per una dimostrazione:

(Puoi scaricare demo e file sorgente dal repository GitHub.)

Quando si affrontano più tipi di terreno, il numero di diverse varianti può superare 300 o più tessere. Disegnare questo molti sprite diversi è sicuramente un processo che richiede tempo, ma il bitmasking delle piastrelle garantisce che l'azione di collocare queste tessere sia rapida ed efficiente.

Con un'implementazione statica di bitmasking, le mappe vengono generate in fase di runtime. Con alcune piccole modifiche, puoi espandere il bitmasking per consentire la visualizzazione di riquadri dinamici durante il gioco. In questo tutorial, tratteremo le basi del bitmasking delle tessere mentre ci avviciniamo a implementazioni più complicate che utilizzano le tessere d'angolo e i tipi di terreno multipli.

Come funziona Tile Bitmasking

Panoramica

Il bitmasking delle tile consiste nel calcolare un valore numerico e nell'assegnare uno sprite specifico basato su quel valore. Ogni tessera guarda le tessere vicine per determinare quale sprite dal set assegnare a se stesso. 

Ogni sprite in un tileset è numerato e il processo di bitmap restituisce un numero corrispondente alla posizione di uno sprite nel tileset. In fase di esecuzione, viene eseguita la procedura di bitmasking e ogni tile viene aggiornato con lo sprite appropriato.

Il foglio sprite qui sopra è composto da tessere terreno con tutte le possibili configurazioni di bordo. I numeri su ciascuna piastrella rappresentano il valore di bitmasking, che impareremo come calcolare nella prossima sezione. Per ora, è importante capire in che modo il valore di bitmasking si riferisce al set di tessere del terreno. Gli sprite sono ordinati sequenzialmente in modo che il valore di bitmasking di 0 restituisce il primo sprite, fino a un valore di 15 che restituisce il 16esimo folletto. 

Calcolo del valore di Bitmasking

Il calcolo di questo valore è relativamente semplice. In questo esempio, stiamo assumendo un singolo tipo di terreno senza pezzi d'angolo. 

Ogni tessera controlla l'esistenza di tessere a Nord, Ovest, Est e Sud, e ogni assegno restituisce un Booleano, dove 0 rappresenta uno spazio vuoto e 1 indica la presenza di un'altra tessera terreno. 

Questo risultato booleano viene quindi moltiplicato per il valore direzionale binario e aggiunto al totale parziale del valore di bitmasking: è più semplice da comprendere con alcuni esempi:

Valori direzionali a 4 bit

  • Nord = 20 = 1
  • Ovest = 21 = 2
  • Est = 22 = 4
  • Sud = 23 = 8

Il quadrato verde nella figura sopra rappresenta la tessera terreno che stiamo calcolando. Iniziamo controllando una tessera a nord. Non ci sono riquadri a nord, quindi il controllo booleano restituisce un valore di 0. Moltiplichiamo 0 per il valore direzionale per Nord, 20 = 1, dandoci 1 * 0 = 0

Per un riquadro terreno circondato interamente da uno spazio vuoto, viene restituito ogni controllo booleano 0, risultante nel numero binario a 4 bit 0000 o 1 * 0 + 2 * 0 + 4 * 0 + 8 * 0 = 0. Esistono 16 combinazioni totali possibili, da 0 a 15, quindi 1st sprite nel tileset sarà usato per rappresentare questo tipo di tessera terreno con un valore di 0.

Una tessera terreno delimitata da una singola tessera al Nord restituisce un valore binario di 0001, o 1 * 1 + 2 * 0 + 4 * 0 + 8 * 0 = 1. Il 2ND sprite nel tileset sarà usato per rappresentare questo tipo di terreno con un valore di 1.

Una tessera terreno delimitata da una tessera a nord e una piastrella ad est restituisce un valore binario di 0101, o 1 * 1 + 2 * 0 + 4 * 1 + 8 * 0 = 5. Il sesto sprite nel tileset verrà utilizzato per rappresentare questo tipo di terreno con un valore di 5.

Una tessera terreno bordata da una tessera ad Est e una tessera ad Ovest restituisce un valore binario di 0110, o 1 * 0 + 2 * 1 + 4 * 1 + 8 * 0 = 6. Il settimo sprite nel tileset verrà utilizzato per rappresentare questo tipo di terreno con un valore di 6.

Assegnazione degli sprite alle piastrelle

Dopo aver calcolato il valore di bitmasking di una tessera, assegniamo lo sprite appropriato al tileset. Questo passaggio finale può essere eseguito in tempo reale mentre la mappa viene caricata, oppure il risultato può essere salvato e caricato nell'editor di piastrelle selezionato per ulteriori modifiche.

La figura a sinistra rappresenta un set di riquadri a terreno singolo a 4 bit che apparirà in sequenza su un foglio di piastrelle. La figura a destra mostra come le tessere appaiono nel gioco dopo che sono state posizionate usando la procedura di mascheramento del bit. Ogni tessera è contrassegnata con il suo valore di maschera di bit per mostrare la relazione tra l'ordine di una tessera sul foglio delle tessere e la sua posizione nel gioco. 

Ad esempio, esaminiamo la tessera nell'angolo in alto a destra della figura a destra. Questa tessera è delimitata da piastrelle ad ovest e Souh. Il controllo booleano restituisce un valore binario di 1010, o 1 * 0 + 2 * 1 + 4 * 0 + 8 * 1 = 10. Questo valore corrisponde all'11esimo sprite nella piastrella.

Complessità di Tileset

Il numero di controlli booleani direzionali richiesti dipende dalla complessità prevista del set di tessere. Ignorando gli angoli, è possibile utilizzare questa soluzione semplificata a 4 bit che richiede solo quattro controlli binari direzionali. 

Ma cosa succede quando vuoi creare un terreno visivamente più attraente? Dovrai occuparti dell'esistenza delle tessere d'angolo, che aumenta la quantità di sprite da 16 a 48. Il seguente esempio di bitmasking a 8 bit richiede otto controlli direzionali booleani per tessera.

Bitmasking a 8 bit con riquadri angolari

Per questo esempio, stiamo creando un set di tessere top-down che descrive il terreno erboso vicino all'oceano. In questo caso, il nostro oceano esiste su uno strato sotto le tessere terreno. Questo ci consente di utilizzare una soluzione single-terrain, pur mantenendo l'illusione che due tipi di terreno si scontrino. 

Una volta che il gioco è in esecuzione e la procedura di mascheramento del bit è completa, gli sprite non cambieranno mai. Questa è un'implementazione statica e senza soluzione di bitmasking in cui tutto avviene prima che il giocatore veda mai le tessere.

Presentazione delle piastrelle angolari

Vogliamo che il terreno sia visivamente più interessante della precedente soluzione a 4 bit, quindi sono necessari i pezzi angolari. Questo extra bit di complessità visiva richiede una quantità esponenziale di lavoro aggiuntivo per l'artista, il programmatore e il gioco stesso. Espandendo ciò che abbiamo appreso dalla soluzione a 4 bit, possiamo capire rapidamente come affrontare la soluzione a 8 bit.

Ecco il foglio sprite completo per le nostre tessere terreno oceaniche. Noti qualcosa di particolare sul numero di tessere? L'esempio a 4 bit di prima risultava in 24 = 16 tessere, quindi questo esempio a 8 bit dovrebbe sicuramente risultare in 28 = 256 tessere, tuttavia ci sono chiaramente meno di quelle lì. 

Mentre è vero che questa procedura di bitmasking a 8 bit comporta 256 possibili valori binari, non tutte le combinazioni richiedono una tessera completamente unica. Il seguente esempio ti aiuterà a spiegare come 256 combinazioni possono essere rappresentate solo da 48 tessere.

Valori direzionali a 8 bit

  • Nord Ovest = 20 = 1
  • Nord = 21 = 2
  • Nord Est = 22 = 4
  • Ovest = 23 = 8
  • Est = 24 = 16
  • Sud Ovest = 25 = 32
  • Sud = 26 = 64
  • Sud Est = 27 = 128

Ora stiamo facendo otto Controlli direzionali booleani. Il riquadro centrale sopra è delimitato da riquadri a nord, nord-est e est, quindi questo controllo booleano restituisce un valore binario di 00010110 o 1 * 0 + 2 * 1 + 4 * 1 + 8 * 0 + 16 * 1 + 32 * 0 + 64 * 0 + 128 * 0 = 22.

La tessera a sinistra in alto è simile alla tessera precedente, ma ora è anche delimitata da tessere a sud-ovest e sud-est. Questo controllo direzionale booleano dovrebbero restituisce un valore binario di 10110110, o 1 * 0 + 2 * 1 + 4 * 1 + 8 * 0 + 16 * 1 + 32 * 1 + 64 * 0 + 128 * 1 = 182

Questo valore è diverso dal riquadro precedente, ma entrambe le tessere sarebbero visivamente identiche, quindi diventa ridondante. 

Per eliminare le ridondanze, aggiungiamo una condizione aggiuntiva al controllo direzionale booleano: quando si controlla la presenza di bordi angolo tessere, dobbiamo anche controllare le tessere vicine nelle quattro direzioni cardinali (direttamente a nord, est, sud o ovest). 

Ad esempio, la tessera a Nord-Est è vicina alle tessere esistenti, mentre le tessere a Sud-Ovest e Sud-Est non lo sono. Ciò significa che le tessere Sud-Ovest e Sud-Est non sono incluse nel calcolo del bitmasking. 

Con questa nuova condizione, questo controllo booleano restituisce un valore binario di 00010110 o 1 * 0 + 2 * 1 + 4 * 1 + 8 * 0 + 16 * 1 + 32 * 0 + 64 * 0 + 128 * 0 = 22 proprio come prima. Ora puoi vedere come le 256 combinazioni possono essere rappresentate solo da 48 tessere.

Ordine delle piastrelle

Un altro problema che potresti notare è che i valori calcolati dalla procedura di bitmasking a 8 bit non sono più correlati all'ordine sequenziale delle tessere nel foglio sprite. Ci sono solo 48 tessere, ma i nostri possibili valori calcolati vanno da 0 a 255, quindi non possiamo più usare il valore calcolato come riferimento diretto quando prendiamo lo sprite appropriato. 

Ciò di cui abbiamo bisogno, quindi, è una struttura dati per contenere l'elenco dei valori calcolati e dei relativi valori di tile corrispondenti. Come vuoi implementarlo dipende da te, ma ricorda che l'ordine in cui controlli le tessere circostanti determina l'ordine in cui le tue tessere devono essere posizionate nel foglio sprite. 

Per questo esempio, controlliamo le tessere confinanti nel seguente ordine: Nord-Ovest, Nord, Nord-Est, Ovest, Est, Sud-Ovest, Sud, Sud-Est. 

Di seguito è riportato il set completo di valori di bitmasking in relazione alle posizioni delle tessere nel nostro foglio sprite (non esitate a utilizzare questi valori nel vostro progetto per risparmiare tempo):

2 = 1, 8 = 2, 10 = 3, 11 = 4, 16 = 5, 18 = 6, 22 = 7, 24 = 8, 26 = 9, 27 = 10, 30 = 11, 31 = 12, 64 = 13, 66 = 14, 72 = 15, 74 = 16, 75 = 17, 80 = 18, 82 = 19, 86 = 20, 88 = 21, 90 = 22, 91 = 23, 94 = 24, 95 = 25 , 104 = 26, 106 = 27, 107 = 28, 120 = 29, 122 = 30, 123 = 31, 126 = 32, 127 = 33, 208 = 34, 210 = 35, 214 = 36, 216 = 37, 218 = 38, 219 = 39, 222 = 40, 223 = 41, 248 = 42, 250 = 43, 251 = 44, 254 = 45, 255 = 46, 0 = 47

Tipi di terreno multipli

Tutti i nostri esempi precedenti presuppongono un singolo tipo di terreno, ma cosa succede se introduciamo un secondo terreno all'equazione? Abbiamo bisogno di un 5-bit soluzione di bitmasking e abbiamo bisogno di definire i nostri due tipi di terreno. Dobbiamo anche assegnare un valore al riquadro centrale che viene conteggiato solo in condizioni specifiche. Ricorda che non stiamo più tenendo conto dello "spazio vuoto" come negli esempi precedenti; le tessere devono ora essere circondate da un'altra tessera su tutti i lati.

La figura sopra mostra un esempio con due tipi di terreno e senza riquadri angolari. Tipo 1 semprerestituisce un valore di 0 ogni volta che viene rilevato durante il controllo direzionale; il valore del riquadro centrale viene calcolato e utilizzato solo se è il tipo di terreno 2. 

Il riquadro centrale nell'esempio precedente è circondato dal tipo di terreno 2 a nord, ovest ed est e dal tipo di terreno 1 a sud. Il riquadro centrale è il tipo di terreno 1, quindi non viene conteggiato. Questo controllo booleano restituisce un valore binario di  00111, o  1 * 1 + 2 * 1 + 4 * 1 + 8 * 0 + 16 * 0 = 7.

In questo esempio, il riquadro centrale è il tipo di terreno 2, quindi verrà conteggiato nel calcolo. Il riquadro centrale è circondato dal tipo di terreno 2 a nord e ad ovest. È anche circondato dal tipo di terreno 1 a est e sud. Questo controllo booleano restituisce un valore binario di  10011, o  1 * 1 + 2 * 1 + 4 * 0 + 8 * 0 + 16 * 1 = 19 .

Implementazione dinamica

Il calcolo del bitmasking può essere eseguito anche durante il gameplay, consentendo cambiamenti in tempo reale nel posizionamento e nell'aspetto delle tessere. Questo è utile per terreni distruttibili e per giochi che consentono di creare e costruire. La procedura iniziale di bitmasking è obbligatoria per tutti i riquadri, ma eventuali calcoli dinamici aggiuntivi devono essere eseguiti solo quando assolutamente necessario. Ad esempio, una tessera terreno distrutta attiverebbe il calcolo del bitmasking solo per le tessere circostanti.

Conclusione

Tile bitmasking è l'esempio perfetto di costruzione di un sistema funzionante per aiutarti nello sviluppo del gioco. Non è qualcosa che influisce direttamente sull'esperienza del giocatore; al contrario, questo metodo di automatizzazione di una parte che richiede molto tempo di progettazione del livello offre un vantaggio prezioso allo sviluppatore. Per dirla semplicemente: tile bitmasking è un modo rapido per fare in modo che il gioco svolga il tuo lavoro sporco, permettendoti di concentrarti su attività più importanti.