Bildepaletter har blitt brukt i datagrafikk fra begynnelsen, og selv om de sjelden finnes i moderne spill, ville en viss klasse problemer være praktisk talt umulig uten dem. I denne opplæringen skal vi bygge en MMO tegndesigner for et 2D-spill ved hjelp av paletter, multi-texturing og shaders.
Merk: Selv om denne opplæringen er skrevet med AS3 og Flash, bør du kunne bruke de samme teknikkene og konseptene i nesten hvilket som helst spillutviklingsmiljø.
Tegnemodellen som ble brukt til demoen, ble laget av Dennis Ricardo Díaz Samaniego, og finner du her: Leviathan. Rigen ble laget av Daren Davoux og kan bli funnet her: Leviathan Rigged.
Klikk på en del av tegnemodellen, og klikk deretter hvor som helst i fargevalgeren. Kan ikke se SWF ovenfor? Sjekk ut denne videoen i stedet:
Full kildefiler er tilgjengelig i nedlasting av kilde.
Demo-implementeringen bruker AS3 og Flash, med Starling-biblioteket for GPU-akselerert rendering og Feathers-biblioteket for brukergrensesnittet. Vår første scene inneholder et bilde av tegnet og fargevalgeren, som vil bli brukt til å endre farger på tegnpaletten.
Representerer farger som bruker paletter, var vanlig i tidlige spill på grunn av maskinvarekrav. Denne teknikken vil kartlegge en verdi fra et bilde til en annen verdi i en palett. Vanligvis vil bildet ha et mindre sett med verdier, for å lagre minnet, og brukes til å slå opp den virkelige verdien i paletten.
Mario sprite nedenfor er ikke laget av rød, oransje og brun - den består av 1, 2 og 3. Når Mario plukker opp en brannblomst, endres paletten slik at 1 representerer hvit og 3 representerer rød. Mario ser annerledes ut, men hans sprite endrer seg ikke.
Siden bruken av 24-biters sannfarge bilderepresentasjon har vært vanlig i mer enn et tiår, kan du lure på hvordan denne teknikken kan være nyttig i dag. Det første åpenbare brukssaken er å lage retrospill, men det er sjelden at du må bruke paletter der. En kunstner kan begrense seg til et bestemt sett med farger, men bruker fortsatt det fulle 24-bitsspekteret, siden det er en enkel måte å håndtere teksturer på i moderne maskinvare.
En teknikk som ble brukt i dagene til palettbilder, var palettbytte: dette ble brukt til å endre det eksisterende bildet til et annet fargevalg. Og mange av dere kan huske hvordan tier av monstre i gamle RPGer bare ble farget annerledes; denne lagrede tiden for artister og brukt mindre minne, noe som muliggjør et større utvalg av monster design. (Muligens kan dette føre til at spillet virker repeterende, skjønt.)
Dette bringer oss til målet med denne opplæringen, som er å la deg få mer variasjon i spillets eiendeler. Vi simulerer en MMO-karakterskaperen, med tilpassbare farger for tegndeler, ved hjelp av en palett.
Å lage palettede bilder er litt vanskeligere enn å lage vanlige bilder. Det er noen begrensninger og tekniske detaljer å passe på.
Først av: Paletter har et begrenset omfang; i denne opplæringen har hver tegnsprite 8 biter - det er 256 mulige verdier - hvorav verdien 255 vil bli brukt til å betegne "gjennomsiktig".
For pikselkunst er dette ikke mye av et problem siden det vanligvis er basert på en begrenset palett valgt av en artist. Mens du tegner, blir palettfarger definert og brukt på bildet.
I eksemplet bruker jeg en 3D-modell; Jeg gjengitt dette i separate lag, deretter kartlagt hvert lag til spektret av en palett. Dette kan gjøres manuelt ved å redigere bildens nivåer for å passe inn i ønsket del av paletten. Disse delene av paletten representerer vi som små gradienter, for å muliggjøre kartlegging fra skygger til høydepunkter.
Dette vil redusere den totale bildedybden på et bilde. Hvis du gjør et tegneserieaktig spill (cel shaded), kan dette være bra, men du kan finne det mangler etter en mer realistisk stil. Vi kan noe avhjelpe dette ved å ofre minnet mens du øker dybden på et bilde til 16 biter - paletten kan være i samme størrelse, og vi vil bruke interpolering for å gi oss mer variasjon.
Implementeringen vår bruker en enkelt 8-bits kanaltekstur for tegnet. For enkelhet gjør vi dette ved å bruke et vanlig PNG-bilde, hvis grønne og blå kanaler er satt til 0, og alt lagres i den røde kanalen (det er derfor eksemplet bildet er rødt). Avhengig av plattformen din, kan du lagre den i et passende enkeltkanalformat. En innledende palett lagres som et 1D-bilde, med en bredde på 256 som representerer alle verdiene vi skal kartlegge.
Hoveddelen er ikke så komplisert. Vi skal slå opp visse verdier i en tekstur basert på en annen tekstur. For dette skal vi bruke multi-texturing og en enkel fragment shader.
(Multi-teksting betyr i hovedsak bruk av flere teksturer mens du tegner et enkelt stykke geometri, og det støttes av GPUer.)
// innstilling av flere teksturer, kontekst er en Stage3D kontekst context.setTextureAt (0, _texture.base); // fs0 i shader context.setTextureAt (1, _palette.base); // fs1 i skyggeren
En annen ting vi må passe på er at vi trenger en måte å gjøre deler av bildene gjennomsiktige. Jeg nevnte tidligere at dette vil bli gjort ved å bruke en spesiell verdi (255) som betyr gjennomsiktig. Fragment shaders har en kommando som vil kaste fragmentet, noe som gjør den usynlig. Vi gjør dette ved å oppdage når verdien er 255.
Dette eksemplet bruker AGAL shader-språket. Det kan være litt vanskelig å forstå, som det er et forsamlingslignende språk. Du kan lære mer om det på Adobe Developer Connection.
// les verdien fra vanlig tekstur (til ft1) tex ft1, v1, fs0 <2d, linear, mipnone, repeat> // trekke teksturverdien (ft1.x) fra // alfa grense (fc0.x definert til å være 0.999 i hovedkoden) sub ft2.x, fc0.x, ft1.x // kaste fragment hvis den representerer masken, // 'kil' gjør at hvis verdien er mindre enn 0 kil ft2.x // les fargen fra paletten ved å bruke verdien fra den vanlige teksten (ft1) tex ft2, ft1, fs1 <2d, nearest, mipnone, repeat> // flerverdig topptekstfarge med palettfarge og lagre den til utgangen mul oc, ft2, v0
Dette kan nå være innkapslet i et egendefinert Starling DisplayObject som inneholder tekstur og palettbildet - slik er det implementert i eksempelkildekoden.
For å endre den faktiske farger av tegnet, må vi endre paletten. Hvis vi ønsket å endre fargen for en bestemt del av tegnet, ville vi ta den opprinnelige gråtonebaletten og endre fargen på segmentet som tilsvarer den delen.
Følgende kode iterates gjennom paletten og bruker riktig farge til hver del:
// gå gjennom paletten for (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);
Våre farger lagres som usignerte heltall som omfatter 8 biter av rød, grønn og blå verdi. For å få dem ut, må vi gjøre litt bitvis operasjoner, heldigvis tilbyr Starling hjelpemetoder for det i Farge
klasse.
For å aktivere valget av tegndeler beholder vi bildedataene i minnet. Vi kan da bestemme delen av tegnet som et museklik samsvarer med ved å lese pikselverdien på det punktet.
var characterColor: uint = _chracterBitmapData.getPixel (touchPoint.x, touchPoint.y); // ta den røde verdien var characterValue: uint = Color.getRed (characterColor); // 255 betyr gjennomsiktig, slik at vi vil bruke det som deselection hvis (characterValue == 255) _sectionSelection = SECTION_NONE; ellers // beregne seksjonen, tar hver seksjon 42 piksler av paletten _sectionSelection = int (characterValue / 42) + 1;
Denne gjennomføringen har noen begrensninger, som kan løses med litt mer arbeid, avhengig av dine behov. Demoen viser ikke sprite-arkanimasjonen, men dette kan legges til uten endring i hovedpalettklassen.
Du har kanskje lagt merke til at gjennomsiktigheten håndteres som synlig og ikke synlig. Dette medfører grove kanter som kanskje ikke passer for alle spill. Dette kan løses ved hjelp av a maske - et gråtonebilde som representerer gjennomsiktighetsverdien, fra svart (helt ugjennomsiktig) til hvit (helt gjennomsiktig). Dette vil imidlertid øke minnekravene litt.
En alternativ teknikk som kan brukes til å endre farge på objektdelene, er å bruke en ekstra tekstur eller en kanal. De brukes som masker eller oppslagstabeller (som i seg selv ligner paletter) for å finne farger som multipliseres med den opprinnelige tekstur. Et eksempel på dette kan ses i denne videoen:
Virkelig interessante effekter kan oppnås ved å animere paletten. I eksempeldemoen brukes dette til å representere tegndelen der fargen endres. Vi kan gjøre dette ved å flytte palettsegmentet som representerer en del av tegnet, og oppretter en sirkulær bevegelse:
// lagre startverdien til palettdelen var tmp: int = _paletteVector [start]; // gå gjennom delenesegmentet på paletten og skift verdiene til venstre for (var i: int = start; i < end; i++) _paletteVector[i] = _paletteVector[i + 1]; // use saved staring value, wrapping around _paletteVector[end] = tmp;
Et ganske fantastisk eksempel på dette kan ses her: Lerretsyklus.
Et ord med forsiktighet: hvis du er sterkt avhengig av denne teknikken for animasjon, kan det være bedre å gjøre det på CPUen fordi opplasting av tekstur til GPU for hver palettendring kan være dyrt.For å få litt ytelse kan vi gruppere flere tegnpaletter (1D) i et enkelt bilde (2D) - dette vil gjøre det mulig for oss å legge til en rekke tegn med minimal endring i renderingstilstanden. Den perfekte brukssaken til dette er et MMO-spill.
Teknikken beskrevet her kan være svært effektiv i 2D MMO-miljøer, spesielt for webspill der nedlastingsstørrelsen er viktig. Jeg håper at jeg klarte å gi deg noen ideer om hva du kan gjøre hvis du tenker på dine teksturer på en annen måte. Takk for at du leste!