I min forrige artikkel, Lag Custom Binary File Formats for spillets data, dekket jeg emnet for ved hjelp av egendefinerte binære filformater for å lagre spillverdier og ressurser. I denne korte opplæringen tar vi en rask titt på hvordan man faktisk leser og skriver binære data.
Merk: Denne opplæringen bruker pseudokode til å demonstrere hvordan man leser og skriver binære data, men koden kan lett oversettes til hvilket programmeringsspråk som støtter grunnleggende fil I / O-operasjoner.
Hvis dette er alt ukjent territorium for deg, vil du legge merke til at noen merkelige operatører blir brukt i koden, spesielt &
, |
, <<
og >>
operatører. Disse er standardbitvise operatører, tilgjengelige i de fleste programmeringsspråk, som brukes til å manipulere binære verdier.
Før vi kan lese og skrive binære data vellykket, er det to viktige begreper som vi trenger å forstå: endianness og strømmer.
Endianitet dikterer rekkefølgen av flere byteverdier i en fil eller i en bit av minne. For eksempel, hvis vi hadde en 16-biters verdi på 0x1020
, den verdien kan enten lagres som 0x10
etterfulgt av 0x20
(big-endian) eller 0x20
etterfulgt av 0x10
(Little endian).
Strømmer er array-lignende objekter som inneholder en sekvens av byte (eller biter i noen tilfeller). Binære data er lest fra og skrevet til disse strømmene. De fleste programmering vil gi en implementering av binære strømmer i en eller annen form; Noen er mer innviklede enn andre, men de gjør i det hele tatt det samme.
La oss begynne med å definere noen egenskaper i vår kode. Ideelt sett bør disse være private eiendommer:
__stream // Den array-lignende gjenstanden som inneholder bytes __endian // Dataendansiteten i strømmen __length // Antallet bytes i strømmen __position // Plasseringen av neste byte for å lese fra strømmen
Her er et eksempel på hva en grunnsklassekonstruktør kan se ut som:
klasse DataInput (stream, endian) __stream = stream __endian = endian __length = stream.length __position = 0
Følgende funksjoner vil lese usignerte heltall fra strømmen:
// Leser en usignert 8-biters heltallfunksjon readU8 () // Kast et unntak hvis det ikke er flere byter tilgjengelig for å lese om (__position> = __length) kaste ny unntak ("...") // Return byte verdi og øk __position eiendomsavkastning __stream [__position ++] // Leser en usignert 16-biters heltallfunksjon readU16 () value = 0 // Endianness må håndteres for flere byteverdier hvis (__endian == BIG_ENDIAN) value | = readU8 () << 8 value |= readU8() << 0 else // LITTLE_ENDIAN value |= readU8() << 0 value |= readU8() << 8 return value // Reads an unsigned 24-bit integer function readU24() value = 0 if( __endian == BIG_ENDIAN ) value |= readU8() << 16 value |= readU8() << 8 value |= readU8() << 0 else value |= readU8() << 0 value |= readU8() << 8 value |= readU8() << 16 return value // Reads an unsigned 32-bit integer function readU32() value = 0 if( __endian == BIG_ENDIAN ) value |= readU8() << 24 value |= readU8() << 16 value |= readU8() << 8 value |= readU8() << 0 else value |= readU8() << 0 value |= readU8() << 8 value |= readU8() << 16 value |= readU8() << 24 return value
Disse funksjonene vil lese signerte heltal fra strømmen:
// Leser en signert 8-biters heltallfunksjon readS8 () // Les usignert verdiverdi = readU8 () // Sjekk om den første (mest signifikante) bit indikerer en negativ verdi hvis (verdi >> 7 == 1) // Bruk «To komplement» for å konvertere verdien verdi = ~ (verdi ^ 0xFF) returverdi // Leser en signert 16-biters heltallfunksjon readS16 () value = readU16 () hvis (verdi >> 15 = = 1) verdi = ~ (verdi ^ 0xFFFF) returverdi // Leser en signert 24-biters heltallfunksjon readS24 () value = readU24 () hvis (verdi >> 23 == 1) verdi = ~ verdi ^ 0xFFFFFF) returverdi // Leser en signert 32-biters heltallfunksjon readS32 () value = readU32 () hvis (verdi >> 31 == 1) verdi = ~ (verdi ^ 0xFFFFFFFF) returverdi
La oss begynne med å definere noen egenskaper i vår kode. (Disse er mer eller mindre de samme som egenskapene vi definerte for å lese binære data.) Ideelt sett bør disse alle være private egenskaper:
__stream // Den array-lignende objekt som vil inneholde bytes __endian // Endianen av dataene i strømmen __position // Plasseringen av neste byte for å skrive til strømmen
Her er et eksempel på hva en grunnsklassekonstruktør kan se ut som:
klasse DataOutput (stream, endian) __stream = stream __endian = endian __position = 0
Følgende funksjoner vil skrive usignerte heltall til strømmen:
// Skriver en usignert 8-biters heltallfunksjon writeU8 (verdi) // Kontrollerer at verdien er usignert og innenfor en 8-biters rekkevidde & = 0xFF // Legg til verdien til strømmen og øk egenskapen __position. __stream [__position ++] = value // Skriver en usignert 16-biters heltallfunksjon writeU16 (verdi) value & = 0xFFFF // Endianness må håndteres for flere byteverdier hvis (__endian == BIG_ENDIAN) writeU8 verdi >> 8) writeU8 (verdi >> 0) annet // LITTLE_ENDIAN writeU8 (verdi >> 0) writeU8 (verdi >> 8) // Skriv en usignert 24-bit heltallfunksjon writeU24 (verdi) verdi & = 0xFFFFFF hvis (__endian == BIG_ENDIAN) writeU8 (verdi >> 16) writeU8 (verdi >> 8) writeU8 (verdi >> 0) ellers writeU8 (verdi >> 0) writeU8 (verdi >> 8) writeU8 (verdi >> 16) // Skriver en usignert 32-biters heltallfunksjon writeU32 (verdi) verdi & = 0xFFFFFFFF hvis (__endian == BIG_ENDIAN) writeU8 (verdi >> 24) writeU8 (verdi >> 16) writeU8 (verdi >> 8) writeU8 (verdi >> 0) ellers writeU8 (verdi >> 0) writeU8 (verdi >> 8) writeU8 (verdi >> 16) writeU8 (verdi >> 24)
Og igjen vil disse funksjonene skrive signerte heltall til strømmen. (Funksjonene er faktisk aliaser av writeU * ()
funksjoner, men de gir API-konsistens med Leser * ()
funksjoner.)
// Skriver en signert 8-bitsverdiefunksjon writeS8 (value) writeU8 (verdi) // Skriver en signert 16-bitsverdiefunksjon writeS16 (verdi) writeU16 (verdi) // Skriver en signert 24-bitverdiefunksjon writeS24 (value) writeU24 (verdi) // Skriver en signert 32-bitsverdiefunksjon writeS32 (verdi) writeU32 (verdi)
Merk: Disse aliasene fungerer fordi binære data alltid lagres som usignerte verdier; for eksempel vil en enkelt byte alltid ha en verdi i området 0 til 255. Konverteringen til signerte verdier gjøres når dataene leses fra en strøm.
Målet mitt med denne korte opplæringen var å utfylle min forrige artikkel om å lage binære filer for spillets data med noen eksempler på hvordan du gjør selve lesing og skriving. Jeg håper det er oppnådd det; Hvis det er mer du vil vite om emnet, vennligst snakk i kommentarene!