Aanpasbare paletten toevoegen eenvoudige variëteit aan de personages van je spel

Beeldpaletten zijn vanaf het begin in computergraphics gebruikt en hoewel ze zelden in moderne games worden gevonden, zou een bepaalde klasse van problemen praktisch zonder hen onmogelijk zijn. In deze tutorial gaan we een MMO-tekenontwerper bouwen voor een 2D-game met behulp van paletten, multi-texturing en shaders.

Notitie: Hoewel deze tutorial geschreven is met behulp van AS3 en Flash, zou je in bijna elke game-ontwikkelomgeving dezelfde technieken en concepten moeten kunnen gebruiken.

Het personagemodel voor de demo is gemaakt door Dennis Ricardo Díaz Samaniego en is hier te vinden: Leviathan. De rig is gemaakt door Daren Davoux en is hier te vinden: Leviathan Rigged.


Eindresultaat voorbeeld

Klik op een gedeelte van het tekenmodel en klik vervolgens ergens in de kleurenkiezer. Kun je de SWF hierboven niet zien? Bekijk deze video in plaats daarvan:

De volledige bronbestanden zijn beschikbaar in de brondownload.


Opstelling

De demo-implementatie maakt gebruik van AS3 en Flash, met de Starling-bibliotheek voor versnelde GPU-rendering en de verenbibliotheek voor de gebruikersinterface. Onze eerste scène bevat een afbeelding van het teken en de kleurenkiezer, die zal worden gebruikt om de kleuren van het tekenpalet te wijzigen.


Paletten in spellen

Kleuren weergeven met paletten was gebruikelijk in vroege games vanwege hardwarevereisten. Deze techniek zou een waarde van een afbeelding naar een andere waarde in een palet toewijzen. Gewoonlijk zou de afbeelding een kleinere reeks waarden hebben, om het geheugen op te slaan, en worden gebruikt om de werkelijke waarde in het palet op te zoeken.

De onderstaande sprite van Mario bestaat niet uit rood, oranje en bruin - het bestaat uit 1, 2 en 3. Wanneer Mario een vuurbloem oppakt, verandert het palet, zodat 1 staat voor wit en 3 voor rood. Mario ziet er anders uit, maar zijn sprite verandert niet echt.

Aangezien het gebruik van 24-bits true-colour-beeldweergave al meer dan een decennium algemeen is, vraagt ​​u zich misschien af ​​hoe deze techniek vandaag nuttig zou kunnen zijn. De eerste voor de hand liggende use case is het maken van retro games, maar het komt zelden voor dat je daar paletten moet gebruiken. Een kunstenaar kan zichzelf beperken tot een bepaalde reeks kleuren, maar toch het volledige 24-bits spectrum gebruiken, omdat dat een eenvoudige manier is om texturen in moderne hardware te verwerken.

Een techniek die tijdens de dagen van paletafbeeldingen werd gebruikt, was paletwisseling: dit werd gebruikt om de bestaande afbeelding in een ander kleurenschema te veranderen. En velen van jullie herinneren je misschien nog hoe rijen monsters in oude RPG's gewoon anders gekleurd waren; deze bespaarde tijd voor artiesten en gebruikte minder geheugen, waardoor een grotere variëteit aan monsterontwerpen mogelijk was. (Dit kan er echter ook toe leiden dat het spel repetitief lijkt.)

Dit brengt ons bij het doel van deze zelfstudie, namelijk om u meer variatie te laten hebben in de activa van uw spel. We zullen een MMO-personage-maker simuleren, met aanpasbare kleuren voor personagedelen, met behulp van een palet.


Gepaletteerde afbeeldingen maken

Het maken van gepalette afbeeldingen is een beetje moeilijker dan het maken van gewone afbeeldingen. Er zijn een paar beperkingen en technische details om op te letten.

Ten eerste: paletten hebben een beperkte reikwijdte; in deze zelfstudie heeft elke tekensprite 8 bits - dat zijn 256 mogelijke waarden - waarvan de waarde '255' zal worden gebruikt om 'transparant' aan te duiden.

Voor pixelkunst is dit geen probleem, omdat het meestal gebaseerd is op een beperkt palet gekozen door een kunstenaar. Terwijl u tekent, worden paletkleuren gedefinieerd en toegepast op de afbeelding.


De afbeelding rechts heeft een aangepast palet dat 's nachts in het spel wordt weergegeven.

In het voorbeeld gebruik ik een 3D-model; Ik heb dit in afzonderlijke lagen weergegeven en vervolgens elke laag toegewezen aan het spectrum van een palet. Dit kan handmatig worden gedaan door de niveaus van de afbeelding aan te passen aan het gewenste deel van het palet. Die delen van het palet die we zullen weergeven als kleine verlopen, om in te stellen van schaduwen naar hooglichten.


In het deelvenster Niveaus wordt duidelijk weergegeven hoe elk tekenonderdeel een segment met afbeeldingswaarden maakt.

Dit zal de algehele beelddiepte van een afbeelding verminderen. Als je een cartoonachtig spel speelt (cel schaduw) kan dit prima, maar misschien ontbreekt het voor een meer realistische stijl. We kunnen dit enigszins verhelpen door geheugen op te offeren en de diepte van een afbeelding te vergroten tot 16 bits - het palet kan even groot blijven, en we zouden interpolatie gebruiken om ons meer variatie te bieden.


Implementatie

Onze implementatie gebruikt een enkele 8-bits kanaaltextuur voor het personage. Voor de eenvoud doen we dit met een gewone PNG-afbeelding, waarvan de groene en blauwe kanalen op 0 zijn ingesteld en alles is opgeslagen in het rode kanaal (vandaar dat de voorbeeldafbeelding rood is). Afhankelijk van uw platform kunt u het opslaan in een geschikte enkel kanaal-indeling. Een eerste palet wordt opgeslagen als een 1D-afbeelding, met een breedte van 256 om alle waarden weer te geven die we in kaart zullen brengen.


Hoofdpersonage afbeelding.
Startpalet voor karakter in ons voorbeeld.

Het grootste deel is niet zo ingewikkeld. We gaan bepaalde waarden opzoeken in één textuur op basis van een andere textuur. Hiervoor gaan we multi-texturing en een eenvoudige fragmentshader gebruiken.

(Multi-texturing betekent in feite het gebruik van meerdere texturen tijdens het tekenen van een enkel stuk geometrie, en het wordt ondersteund door GPU's.)

 // instellen van meerdere texturen, context is een Stage3D context context.setTextureAt (0, _texture.base); // fs0 in de shader-context.setTextureAt (1, _palette.base); // fs1 in de arcering

Een ander ding waar we op moeten letten, is dat we een manier nodig hebben om delen van de afbeeldingen transparant te maken. Ik heb eerder gezegd dat dit gedaan zal worden met een speciale waarde (255) wat transparant betekent. Fragment shaders hebben een opdracht die het fragment weggooit, waardoor het onzichtbaar wordt. We doen dit door te detecteren wanneer de waarde 255 is.

In dit voorbeeld wordt de AGAL-shadertaal gebruikt. Het kan een beetje moeilijk te begrijpen zijn, omdat het een assembly-achtige taal is. U kunt er meer over te weten komen op de Adobe Developer Connection.

 // lees de waarde van de standaardtextuur (naar ft1) tex ft1, v1, fs0 <2d, linear, mipnone, repeat> // trek de texture-waarde (ft1.x) af van // alpha threshold (fc0.x gedefinieerd als 0.999 in hoofdcode) sub ft2.x, fc0.x, ft1.x // verwijder fragment als dit het masker vertegenwoordigt, // 'kil' doet dat als de waarde kleiner is dan 0 kil ft2.x // lees de kleur uit het palet met behulp van de waarde uit de reguliere structuur (ft1) tex ft2, ft1, fs1 <2d, nearest, mipnone, repeat> // vermenigvuldig vertex-kleuren met paletkleur en sla deze op in de uitvoer mul oc, ft2, v0

Dit kan nu worden ingekapseld in een aangepast Starling DisplayObject dat de textuur en het palet bevat - zo is het geïmplementeerd in de voorbeeldbroncode.

Om de werkelijke te veranderen kleuren van het personage, moeten we het palet wijzigen. Als we de kleur voor een bepaald deel van het teken wilden wijzigen, gebruikten we het originele grijsschaalpalet en veranderden we de kleur van het segment dat overeenkomt met dat deel.


Het deel van het palet dat overeenkomt met haar is nu gekleurd.

De volgende code doortekent het palet en past de juiste kleur toe op elk onderdeel:

 // ga door het palet voor (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); 

Onze kleuren worden opgeslagen als niet-getekende gehele getallen die 8 bits rode, groene en blauwe waarde bevatten. Om ze eruit te krijgen, zouden we wat bitwise-bewerkingen moeten doen, gelukkig biedt Starling helper-methoden hiervoor in de Kleur klasse.

Om de selectie van tekenonderdelen in te schakelen, houden we de afbeeldingsgegevens in het geheugen. Vervolgens kunnen we bepalen met welk deel van het teken een muisklik overeenkomt door de pixelwaarde op dat punt te lezen.

 var characterColor: uint = _chracterBitmapData.getPixel (touchPoint.x, touchPoint.y); // neem de rode waarde var characterValue: uint = Color.getRed (characterColor); // 255 betekent transparant, dus gebruiken we dat als deselectie als (characterValue == 255) _sectionSelection = SECTION_NONE;  else // bereken de sectie, elke sectie neemt 42 pixels van het palet _sectionSelection = int (characterValue / 42) + 1; 

We kunnen meer doen

Deze implementatie heeft een aantal beperkingen, die kunnen worden opgelost met een beetje meer werk, afhankelijk van uw behoeften. De demo toont de sprite-bladanimatie niet, maar deze kan zonder aanpassing aan de hoofdpaletklasse worden toegevoegd.

U hebt misschien gemerkt dat de transparantie wordt behandeld zoals zichtbaar en Niet zichtbaar. Dit veroorzaakt ruwe randen die mogelijk niet geschikt zijn voor alle games. Dit kan worden opgelost met behulp van een masker - een grijsschaalafbeelding die de transparantiewaarde vertegenwoordigt, van zwart (volledig ondoorzichtig) tot wit (volledig transparant). Dit zal echter de geheugenvereisten iets verhogen.

Een alternatieve techniek die kan worden gebruikt om de kleur van de objectonderdelen te wijzigen, is om een ​​extra textuur of een kanaal te gebruiken. Ze worden gebruikt als maskers of opzoektabellen (die op zichzelf lijken op paletten) om kleurwaarden te vinden die worden vermenigvuldigd met de oorspronkelijke structuur. Een voorbeeld hiervan is te zien in deze video:

Echt interessante effecten kunnen worden bereikt door het palet te animeren. In de voorbeelddemo wordt dit gebruikt om het karaktergedeelte weer te geven waar de kleur wordt gewijzigd. We kunnen dit doen door het paletgedeelte dat een gedeelte van het teken vertegenwoordigt te verschuiven, waardoor een cirkelvormige beweging ontstaat:

 // sla de startwaarde van de paletsectie op var tmp: int = _paletteVector [start]; // ga door het sectiesegment van het palet en verplaats de waarden naar links voor (var i: int = start; i < end; i++)  _paletteVector[i] = _paletteVector[i + 1];  // use saved staring value, wrapping around _paletteVector[end] = tmp;

Een tamelijk verbluffend voorbeeld hiervan is hier te zien: Canvas Cycle.

Een woord van waarschuwing: als je voor deze animatie sterk op deze techniek vertrouwt, is het misschien beter om dit op de CPU te doen, omdat het uploaden van de textuur naar de GPU voor elke paletverandering duur kan zijn.

Om wat prestaties te krijgen, kunnen we meerdere karakterpaletten (1D) groeperen in een enkele afbeelding (2D) - dit zou ons in staat stellen een verscheidenheid aan karakters toe te voegen met minimale veranderingen in de weergavestatus. De perfecte use case hiervoor is een MMO-game.


Conclusie

De hier beschreven techniek kan zeer effectief zijn in 2D MMO-omgevingen, vooral voor webgames waarbij de downloadgrootte van groot belang is. Ik hoop dat ik je wat ideeën heb kunnen geven over wat je kunt doen als je op een andere manier over je texturen nadenkt. Bedankt voor het lezen!