Le palette di immagini sono state utilizzate in computer grafica sin dall'inizio e, anche se raramente si trovano nei giochi moderni, una certa classe di problemi sarebbe praticamente impossibile senza di essi. In questo tutorial costruiremo un designer di personaggi MMO per un gioco 2D usando palette, multi-texturing e shader.
Nota: Sebbene questo tutorial sia scritto usando AS3 e Flash, dovresti essere in grado di utilizzare le stesse tecniche e concetti in quasi tutti gli ambienti di sviluppo di giochi.
Il modello del personaggio utilizzato per la demo è stato realizzato da Dennis Ricardo Díaz Samaniego e può essere trovato qui: Leviatano. L'impianto è stato realizzato da Daren Davoux e può essere trovato qui: Leviathan Rigged.
Fare clic su una sezione del modello di carattere, quindi fare clic in qualsiasi punto del selettore di colori. Non riesci a vedere il SWF sopra? Guarda questo video invece:
I file di origine completi sono disponibili nel download di origine.
L'implementazione demo utilizza AS3 e Flash, con la libreria Starling per il rendering accelerato GPU e la libreria Feathers per l'interfaccia utente. La nostra scena iniziale contiene un'immagine del personaggio e il selettore di colori, che sarà usato per cambiare i colori della tavolozza dei caratteri.
Rappresentare i colori usando le tavolozze era comune nei primi giochi a causa dei requisiti hardware. Questa tecnica mapperebbe un valore da un'immagine ad un altro valore in una tavolozza. Di solito l'immagine avrebbe un set di valori più piccolo, per salvare la memoria e essere utilizzata per cercare il valore reale nella tavolozza.
Lo sprite di Mario qui sotto non è composto da rosso, arancione e marrone - è composto da 1, 2 e 3. Quando Mario prende un fiore di fuoco, la tavolozza cambia in modo che 1 rappresenti il bianco e 3 rappresenti il rosso. Mario sembra diverso, ma il suo sprite in realtà non cambia.
Poiché l'uso della rappresentazione di immagini a 24 bit a colori reali è stato comune per più di un decennio, ci si potrebbe chiedere come questa tecnica possa essere utile oggi. Il primo caso di utilizzo ovvio è la creazione di giochi retrò, ma raramente è necessario utilizzare le tavolozze lì. Un artista può limitarsi a un determinato insieme di colori, ma utilizzare comunque lo spettro completo a 24 bit, poiché è un modo semplice per gestire le trame nell'hardware moderno.
Una tecnica utilizzata durante i giorni delle immagini della palette era lo scambio delle palette: questo era usato per cambiare l'immagine esistente in una combinazione di colori diversa. E molti di voi ricorderanno come i livelli di mostri nei vecchi giochi di ruolo erano appena colorati in modo diverso; questo tempo risparmiato per gli artisti e usato meno memoria, consentendo una maggiore varietà di disegni di mostri. (Probabilmente questo potrebbe far sembrare il gioco ripetitivo, però).
Questo ci porta all'obiettivo di questo tutorial, che ti consente di avere più varietà nelle risorse del tuo gioco. Simuleremo un creatore di personaggi MMO, con colori personalizzabili per le parti dei personaggi, usando una tavolozza.
Realizzare immagini palettizzate è un po 'più difficile che fare immagini normali. Ci sono alcune limitazioni e dettagli tecnici a cui prestare attenzione.
Prima di tutto: le tavolozze hanno un ambito limitato; in questo tutorial, ogni personaggio sprite ha 8 bit, ovvero 256 valori possibili, di cui il valore '255' sarà usato per indicare 'trasparente'.
Per la pixel art questo non è un gran problema dato che di solito è basato su una tavolozza limitata scelta da un artista. Mentre stai disegnando, i colori della tavolozza sono definiti e applicati all'immagine.
Nell'esempio sto usando un modello 3D; L'ho reso in livelli separati, quindi ho mappato ogni livello nello spettro di una tavolozza. Questo può essere fatto manualmente modificando i livelli dell'immagine per adattarla alla parte desiderata della tavolozza. Quelle parti della palette rappresenteremo come piccole sfumature, per abilitare la mappatura dalle ombre alle alte luci.
Ciò ridurrà la profondità complessiva dell'immagine di un'immagine. Se stai facendo un gioco a fumetti (cel shaded) questo può andar bene, ma potresti trovarlo carente per uno stile più realistico. Possiamo in qualche modo rimediare a ciò sacrificando la memoria mentre aumentiamo la profondità di un'immagine a 16 bit - la tavolozza potrebbe rimanere della stessa dimensione, e useremmo l'interpolazione per fornirci più varietà.
La nostra implementazione utilizza una singola trama canale a 8 bit per il personaggio. Per semplicità, lo faremo usando un'immagine PNG normale, i cui canali verdi e blu sono impostati su 0, e tutto è memorizzato nel canale rosso (ecco perché l'immagine di esempio è in rosso). A seconda della piattaforma, è possibile salvarlo in un formato di canale singolo appropriato. Una tavolozza iniziale viene salvata come immagine 1D, con una larghezza di 256 per rappresentare tutti i valori che mapperemo.
La parte principale non è così complicata. Stiamo andando a cercare determinati valori in una texture basata su un'altra texture. Per questo useremo multi-texturing e un semplice shader di frammenti.
(La multi-texturing significa essenzialmente l'utilizzo di più trame mentre si disegna un singolo pezzo di geometria ed è supportato dalle GPU.)
// impostazione di trame multiple, contesto è un contesto Stage3D context.setTextureAt (0, _texture.base); // fs0 nello shader context.setTextureAt (1, _palette.base); // fs1 nello shader
Un'altra cosa che dobbiamo cercare è che abbiamo bisogno di un modo per rendere trasparenti parti delle immagini. Ho detto prima che ciò sarà fatto usando un valore speciale (255) che significa trasparente. Gli shader dei frammenti hanno un comando che scarterà il frammento, rendendolo invisibile. Lo faremo rilevando quando il valore è 255.
Questo esempio utilizza il linguaggio shader AGAL. Può essere un po 'difficile da capire, in quanto è un linguaggio di assemblaggio. Puoi saperne di più su Adobe Developer Connection.
// legge il valore dalla texture regolare (a ft1) tex ft1, v1, fs0 <2d, linear, mipnone, repeat> // sottrarre il valore della trama (ft1.x) da // alpha threshold (fc0.x definito come 0.999 nel codice principale) sub ft2.x, fc0.x, ft1.x // scartare il frammento se rappresenta la maschera, // 'kil' lo fa se il valore è inferiore a 0 kil ft2.x // legge il colore dalla tavolozza usando il valore della trama normale (ft1) tex ft2, ft1, fs1 <2d, nearest, mipnone, repeat> // moltiplica il colore del vertice con il colore della tavolozza e lo memorizza nell'output mul oc, ft2, v0
Questo può ora essere incapsulato in uno Starling DisplayObject personalizzato che contiene la trama e l'immagine della tavolozza - questo è il modo in cui è implementato nel codice sorgente di esempio.
Per cambiare il reale colori del personaggio, avremo bisogno di cambiare la tavolozza. Se volessimo cambiare il colore per una particolare parte del personaggio, prenderemo la tavolozza della scala di grigi originale e cambieremo la colorazione del segmento che corrisponde a quella parte.
Il codice seguente scorre la palette e applica il colore appropriato a ciascuna parte:
// passa attraverso la palette per (var i: int = 0; i < _paletteVector.length; i++) // 42 is the length of a segment in the palette var color:uint = _baseColors[int(i / 42)]; // extract the RGB values from the segment color value and // multiply original grayscale palette var r:uint = Color.getRed(color) * _basePaletteVector[i]; var g:uint = Color.getGreen(color) * _basePaletteVector[i]; var b:uint = Color.getBlue(color) * _basePaletteVector[i]; // create a new palette color by joining color components _paletteVector[i] = Color.rgb(r, g, b);
I nostri colori vengono salvati come interi senza segno che comprendono 8 bit di valore rosso, verde e blu. Per farli uscire avremmo bisogno di fare alcune operazioni bit a bit, per fortuna Starling offre metodi di aiuto per questo nel Colore
classe.
Per abilitare la selezione delle parti dei caratteri, manteniamo i dati dell'immagine in memoria. Possiamo quindi determinare la sezione del carattere a cui corrisponde un clic del mouse leggendo il valore del pixel in quel punto.
var characterColor: uint = _chracterBitmapData.getPixel (touchPoint.x, touchPoint.y); // assume il valore rosso var characterValue: uint = Color.getRed (characterColor); // 255 significa trasparente, quindi lo useremo come deselezione se (characterValue == 255) _sectionSelection = SECTION_NONE; else // calcola la sezione, ogni sezione prende 42 pixel della palette _sectionSelection = int (characterValue / 42) + 1;
Questa implementazione ha alcune limitazioni, che possono essere risolte con un po 'di lavoro in più a seconda delle esigenze. La demo non mostra l'animazione del foglio sprite, ma può essere aggiunta senza modifiche alla classe della palette principale.
Potresti aver notato che la trasparenza è gestita come visibile e non visibile. Ciò causa bordi grezzi che potrebbero non essere adatti a tutti i giochi. Questo può essere risolto usando a maschera - un'immagine in scala di grigi che rappresenta il valore di trasparenza, dal nero (totalmente opaco) al bianco (totalmente trasparente). Ciò tuttavia aumenterà un po 'i requisiti di memoria.
Una tecnica alternativa che può essere usata per cambiare il colore delle parti dell'oggetto è usare una trama aggiuntiva o un canale. Vengono utilizzati come maschere o tabelle di ricerca (che sono simili alle palette) per trovare valori di colore che verranno moltiplicati con la trama originale. Un esempio di questo può essere visto in questo video:
Effetti davvero interessanti possono essere ottenuti animando la tavolozza. Nell'esempio demo questo è usato per rappresentare la parte del personaggio in cui viene cambiato il colore. Possiamo farlo spostando il segmento della palette che rappresenta una sezione del personaggio, creando un movimento circolare:
// salva il valore iniziale della sezione palette var tmp: int = _paletteVector [start]; // passa attraverso il segmento di sezione della palette e sposta i valori a sinistra per (var i: int = start; i < end; i++) _paletteVector[i] = _paletteVector[i + 1]; // use saved staring value, wrapping around _paletteVector[end] = tmp;
Un esempio piuttosto sorprendente di questo può essere visto qui: Canvas Cycle.
Una parola di cautela: se ti affidi fortemente a questa tecnica per l'animazione, potrebbe essere meglio farlo sulla CPU, perché caricare la texture sulla GPU per ogni cambio di tavolozza può essere costoso.Per ottenere delle prestazioni possiamo raggruppare più palette di caratteri (1D) in una singola immagine (2D): ciò ci consentirebbe di aggiungere una varietà di caratteri con modifiche minime allo stato di rendering. Il caso d'uso perfetto per questo è un gioco MMO.
La tecnica qui descritta può essere molto efficace negli ambienti MMO 2D, specialmente per i giochi web in cui le dimensioni del download sono molto importanti. Spero di essere riuscito a darti qualche idea su cosa puoi fare se pensi alle tue trame in un modo diverso. Grazie per aver letto!