Opprett egendefinerte binære filformater for spillets data

Ditt spill har data - sprites, lydeffekter, musikk, tekst - og du må lagre den på en eller annen måte. Noen ganger kan du inkapslere alt til en enkelt SWF, .unity3d eller EXE fil, men i noen tilfeller vil det ikke være egnet. I denne tekniske opplæringen ser vi på bruk av tilpassede binære filer til dette formålet.

Merk: Denne opplæringen antar at du har en grunnleggende forståelse av biter og byte. Sjekk ut en introduksjon til binær, hexadecimal og mer og forstå bitwise operatører på Activetuts + hvis du trenger å revidere!


Fordeler og ulemper med egendefinerte binære filer

Det er noen fordeler og ulemper ved å bruke egendefinerte binære filformater.

Å skape noe som en ressursbeholder (som denne opplæringen vil gjøre) vil redusere disk / server thrashing og vil vanligvis gjøre ressursbelastning mye lettere fordi flere filer ikke trenger å lastes. Tilpassede filformater kan også legge til et ekstra lag med sikkerhet i form av obfuscation til spillressurser.

På forsiden må du faktisk generere de egendefinerte filene på en eller annen måte før du kan bruke dem i et spill, men det er ikke så vanskelig som det kan høres - spesielt hvis du eller noen du kjenner, kan skape noe som en JAR-fil som kan slippes inn i en byggprosess med relativ letthet.


Forstå primitive datatyper

Før du kan begynne å designe dine egne binære filformater, trenger du en forståelse av de primitive datatyper (byggeklosser) som er tilgjengelige for deg. Antall primitive datatyper er faktisk ubegrensede, men det er et vanlig sett som de fleste programmerere er kjent med og bruker, og disse datatyper representerer vanligvis flere ganger 8 biter.

Som du kan se, gir disse primitive datatyper et bredt spekter av heltallverdier, og du finner dem som kjernen i de fleste binære filspesifikasjoner. Det er noen få primitive datatyper, for eksempel flytende poengstall, men de totale datatyper som er oppført ovenfor, er mer enn tilstrekkelige for denne introduksjonen og for de fleste binære filformater.


Forstå strukturerte datatyper

Strukturerte datatyper (eller komplekse datatyper) representerer bestemte elementer (biter) av en binær fil, og de består av primitive datatyper eller andre strukturerte datatyper.

Du kan tenke på strukturerte datatyper som objekter eller klasseksempler i et programmeringsspråk, hvor hvert objekt erklærer et sett med egenskaper. Strukturerte datatyper kan visualiseres ved hjelp av enkel objektnotasjon.

Her er et eksempel på en fiktiv filoverskrift:

 HEADER signatur U24 versjon U8 lengde U32

Så her heter den strukturerte datatypen OVERSKRIFT og den har tre egenskaper merket signatur, versjon og lengde. Hver egenskap i dette eksemplet er erklært som en primitiv datatype, men egenskaper kan også deklareres som strukturerte datatyper.

Hvis du er en programmerer, begynner du sannsynligvis hvor lett det ville være å representere en binær fil i et OOP-basert programmeringsspråk; la oss ta en rask titt på hvordan dette OVERSKRIFT datatype kan være representert i Java:

 Klassehode offentlig int signatur; // U24 public int versjon; // U8 offentlig lang lengde; // U32

Utforming av en tilpasset binærfil

På dette punktet bør du være kjent med grunnleggende om binære filstrukturer, så nå er det på tide å se på prosessen med å designe et fungerende tilpasset filformat. Dette filformatet vil bli designet for å holde en samling av spillressurser, inkludert bilder og lyder.

Overskriften

Det første som skal utformes, er en datastruktur for filoverskriften, slik at filen kan identifiseres før resten av filen er lastet inn i minnet. Ideelt sett skal filhodet minst inneholde et signaturfelt og et versjonsfelt:

 HEADER signatur U24 versjon U8

Filnavnet du velger å bruke, er opp til deg: det kan være et hvilket som helst antall byter, men de fleste filformater har en menneskelig lesbar signatur som inneholder tre eller fire ASCII-tegn. For mitt formål, signatur feltet vil inneholde tegnkoder på tre ASCII-tegn (en byte per tegn) og vil representere strengen "RES" (kort for "RESOURCE"), slik at byteverdiene vil bli 0x52, 0x45 og 0x53.

De versjon feltet vil i utgangspunktet være 0x01 fordi dette er Versjon 1 av filformatet.

Resursfilen er faktisk en strukturert datatype som inneholder en header og vil senere inneholde andre elementer. Det ser for øyeblikket ut som dette:

 FIL header HEADER

Bilder

Den neste tingen vil vi se på er datastrukturen for bilder.

Resursfilen lagrer en rekke ARGB-fargeværdier (en per piksel) og tillater at dataene eventuelt komprimeres ved hjelp av ZLIB-algoritmen. Bildedimensjonene må også inkluderes i filen sammen med en identifikator for bildet (slik at bildet kan nås etter at det er lastet inn i minnet):

 BILDE id STRING bredde U16 høyde U16 komprimert U8 dataLength U32 data U8 [dataLength]

Det er et par ting i den strukturen som trenger din oppmerksomhet; den første er U8 [dataLength] En del av strukturen og den andre er STRING datastruktur brukt til id, som ikke var definert i tabellen over datatyper ovenfor.

Den tidligere er grunnleggende array notasjon - det betyr bare det dataLength antall U8 verdier må leses fra filen. De data feltet inneholder bildepiksler, og komprimert feltet angir om den data feltet er komprimert. Hvis komprimert verdien er 0x01 og så data feltet er ZLIB komprimert, ellers kan dekoderen påta seg data feltet er ikke komprimert. Fordelen med å bruke ZLIB komprimering her er BILDE filstrukturen vil ende opp med å være en tilsvarende størrelse som en PNG-kodet versjon av bildet.

De STRING datastruktur er som følger:

 STRING dataLength U16 data U8 [dataLength]

For dette filformatet vil alle strenger bli kodet som UTF-8 og bytes av den kodede strengen vil bli plassert i data feltet av STRING data struktur. De dataLength feltet angir antall byte i data felt.

Resursfilstrukturen ser nå slik ut:

 FIL header HEADER imageCount U16 bildeListe bilde [imageCount]

Som du kan se filen inneholder nå en header, en ny imageCount felt som angir antall bilder i filen, og en ny imageList feltet for bildene. Dette i seg selv ville være et nyttig filformat for å lagre flere bilder, men det ville være enda mer nyttig hvis det hadde flere ressurstyper, så vil det nå se på å legge til lyder på filen.

lyder

Lydene lagres i filen på samme måte som bilder, men i stedet for å lagre råpikselfargeværdier, lagrer filen rå lydprøver i varierende bitoppløsninger:

 SOUND id STRING dataFormat U8 dataLength U32 // 8-bit samples if (dataFormat == 0x00) data U8 [dataLength] // 16-bit samples if (dataFormat == 0x01) data U16 [dataLength] // 32-biters prøver hvis (dataFormat == 0x02) data U32 [dataLength]

Oh godhet, betingede utsagn! Fordi det dataformat feltet indikerer bithastigheten til lyden, formatet til data feltet må være variabelt, og det er der den enkle og programmeringsvenlige, betingede setningssyntaxen kommer inn i spill.

Når du ser på datastrukturen, kan du enkelt se hvilket format den data feltverdier (lydprøver) vil bli brukt, gitt en bestemt dataformat verdi. Når du legger til felt som dataformat til en datastruktur er verdiene de feltene kan inneholde helt opp til deg. Verdiene 0x01, 0x02 og 0x03 brukes i dette eksemplet, bare fordi de er de første ubrukte verdiene som er tilgjengelige i byten.

Resursfilstrukturen ser nå slik ut:

 FIL header HEADER imageCount U16 bildeListe bilde [imageCount] soundCount U16 lydliste SOUND [soundCount]

Generiske data

Den siste tingen som legges til denne ressursfilstrukturen, er generisk data; Dette vil tillate at diverse spillrelaterte data (i forskjellige formater) skal pakkes inn i filen.

Som BILDE Datastruktur denne nye DATA strukturen vil støtte valgfri ZLIB-komprimering fordi tekstbaserte data som JSON og XML generelt har nytte av komprimering, og dette vil også forvirre dataene i filen:

 DATA id STRING komprimert U8 dataFormat U8 dataLength U32 data U8 [dataLength]

De komprimert feltet angir om data feltet er komprimert: en verdi på 0x01 betyr data feltet er ZLIB komprimert.

De dataformat Indikerer formatet på dataene, og verdiene dette feltet kan inneholde, er opp til deg. Du kan for eksempel bruke 0x00 for rå tekst, 0x01 for XML og 0x02 for JSON. En enkelt usignert byte (U8) kan holde 256 forskjellige verdier, og det bør være mer enn nok for alle de forskjellige dataformatene du kanskje vil bruke i et spill.

Den endelige ressursfilstrukturen ser slik ut:

 FIL header HEADER imageCount U16 bildeListe bilde [imageCount] soundCount U16 lydliste SOUND [soundCount] dataCount U16 dataList DATA [dataCount]

Så langt som filformater går, er dette relativt enkelt - men det er funksjonelt og det demonstrerer hvordan filformatene kan representeres og struktureres på en fornuftig og forståelig måte.


Forstå byteordrer

Det er en viktigere ting som du kanskje trenger å vite om binære filer: multibyteverdier lagret i binære filer kan bruke en av to byteordrer (dette kalles også "endian"). Byteordren kan enten være LSB (minste signifikant byte eller "small endian") eller MSB (mest signifikant byte først eller "big endian"). Forskjellen mellom de to byteordrene er ganske enkelt den rekkefølgen der byte lagres.

For eksempel består en 24-biters RGB-fargeverdi av tre byte, en byte for hver fargekanal. Byte rekkefølge av en fil bestemmer om de byte lagres i filen som RGB (big-endian) eller BGR (liten endian).

Mange moderne programmeringsspråk gir en API som lar deg bytte rekkefølgen mens du leser en fil i minnet, slik at lesing av multibyteverdier fra en binær fil ikke er noe programmerere vanligvis trenger å være bekymret for. Men hvis du leser en byte-by-byte, må du være oppmerksom på filens byteordre.

Følgende Java-kode demonstrerer hvordan du leser en 24-biters verdi (i dette tilfellet en RGB-farge) fra en fil mens du vurderer filens bytebestilling:

 bigEndian boolean = true; int readU24 (InputStream input) kaster IOException int verdi = 0; hvis (bigEndian) value | = input.read () << 16; // red value |= input.read() << 8; // green value |= input.read() << 0; // blue  else // little endian  value |= input.read() << 0; // blue value |= input.read() << 8; // green value |= input.read() << 16; // red  return value; 

Den faktiske lesing og skriving av binære filer er utenfor omfanget av denne opplæringen, men i det eksemplet bør du kunne se hvordan rekkefølgen av de tre byte med en 24-biters verdi vendes avhengig av byte rekkefølgen (endian) av en fil. Er det noen fordeler med å bruke en byteordre i stedet for den andre? Vel, ikke egentlig - byteordrer er bare bekymret for maskinvare ikke programvare.

Hvor du går herfra, er opp til deg, men jeg håper denne opplæringen har gjort tilpassede filformater litt mindre skummelt å vurdere å bruke i dine egne spill!