Slik bruker du Tile Bitmasking til å automatisere dine nivåoppsett

Å lage en visuelt tiltalende og variert fliserett er en tidkrevende prosess, men resultatene er ofte verdt det. Men selv etter å ha skapt kunsten, må du likevel styre alt sammen i ditt nivå! 

Du kan plassere hver flis, en etter en, for hånd, eller du kan automatisere prosessen ved å bruke bitmasking, så du trenger bare å tegne terrengets form.

Hva er Tile Bitmasking?

Tile bitmasking er en metode for automatisk å velge riktig sprite fra et definert fliserett. Dette lar deg plassere en generisk plassholderflis overalt hvor du ønsker at en bestemt type terreng skal vises i stedet for å plassere et potensielt stort utvalg av forskjellige fliser. 

Se denne videoen for en demonstrasjon:

(Du kan laste ned demoer og kildefiler fra GitHub repo.)

Når det gjelder flere typer terreng, kan antallet forskjellige variasjoner overstige 300 eller flere fliser. Tegning av dette mange forskjellige sprites er definitivt en tidkrevende prosess, men flismaskering sikrer at handlingen med å plassere disse flisene er rask og effektiv.

Ved en statisk implementering av bitmasking genereres kart ved kjøretid. Med noen få små tweaks kan du utvide bitmasking for å tillate dynamiske fliser som endrer seg under spillingen. I denne opplæringen vil vi dekke det grunnleggende om flisebitmasking mens vi arbeider mot mer kompliserte implementeringer som bruker hjørnefliser og flere terrengtyper.

Hvordan Tile Bitmasking Works

Oversikt

Tile bitmasking handler om å beregne en numerisk verdi og tildele en bestemt sprite basert på den verdien. Hver flis ser på sine nabobilder for å bestemme hvilken sprite fra settet som skal tilordnes seg selv. 

Hver sprite i et fliserett er nummerert, og bitmaskingsprosessen returnerer et tall som svarer til posisjonen til en sprite i fliset. Ved kjøretid utføres bitmaskingsprosedyren, og alle fliser blir oppdatert med passende sprite.

Sprite-arket ovenfor består av terrengfliser med alle mulige grensekonfigurasjoner. Tallene på hver flis representerer bitmaskingsverdien, som vi lærer å beregne i neste avsnitt. For nå er det viktig å forstå hvordan bitmaskingsverdien er knyttet til terrengfliserettet. De sprites er bestilt sekvensielt slik at en bitmasking verdi på 0 returnerer den første sprite, helt til en verdi av 15 som returnerer 16th sprite. 

Beregning av bitmaskingsverdien

Beregning av denne verdien er relativt enkel. I dette eksemplet antar vi en enkelt terrengtype uten hjørnebrikker. 

Hver flis kontrollerer eksistensen av fliser til Nord, Vest, Øst og Sør, og hver sjekk returnerer en boolsk, hvor 0 representerer en tom plass og 1 betyr tilstedeværelse av en annen terrengfliser. 

Dette boolske resultatet blir så multiplisert med den binære retningsverdien og legges til den totale summen av bitmaskingsverdien. Det er lettere å forstå med noen eksempler:

4-bits retningsverdier

  • Nord = 20 = 1
  • Vest = 21 = 2
  • Øst = 22 = 4
  • Sør = 23 = 8

Det grønne torget i figuren ovenfor representerer terrengflisen vi beregner. Vi starter med å se etter en flis til nord. Det er ingen flis i nord, så den boolske sjekken returnerer en verdi på 0. Vi multipliserer 0 med retningsverdien for Nord, 20 = 1, gi oss 1 * 0 = 0

For en terrengfliser omgitt av tomt rom, returnerer hver boolsk sjekk 0, resulterer i det 4-bitte binære nummeret 0000 eller 1 * 0 + 2 * 0 + 4 * 0 + 8 * 0 = 0. Det er 16 totalt mulige kombinasjoner, fra 0 til 15, så den 1st Sprite i fliset vil bli brukt til å representere denne typen terrengfliser med en verdi på 0.

En terrengfliser grenser til en enkelt flis til nord returnerer en binær verdi på 0001, eller 1 * 1 + 2 * 0 + 4 * 0 + 8 * 0 = 1. Den 2nd Sprite i fliset vil bli brukt til å representere denne typen terreng med en verdi på 1.

En terrengfliser grenser til en flate til nord og en flis til øst gir en binær verdi på 0101, eller 1 * 1 + 2 * 0 + 4 * 1 + 8 * 0 = 5. Den 6. sprite i fliset vil bli brukt til å representere denne typen terreng med en verdi på 5.

En terrengfliser som grenser mot en flis mot øst og en flis til vest, returnerer en binær verdi på 0110, eller 1 * 0 + 2 * 1 + 4 * 1 + 8 * 0 = 6. Den 7. sprite i fliset vil bli brukt til å representere denne typen terreng med en verdi på 6.

Tilordne Sprites til fliser

Etter å ha regnet en flises bitmaskingsverdi, tilordner vi passende sprite fra flisetettet. Dette siste trinnet kan utføres i sanntid etter hvert som kartet laster, eller resultatet kan lagres og lastes inn i fliseditoren din for videre redigering.

Figuren til venstre representerer en 4-bits, enkelt terrengfliser som det ser ut som en sekvens på et fliserark. Figuren til høyre viser hvordan flisene ser i spillet etter at de er plassert ved hjelp av bitmasking-prosedyren. Hver flis er merket med sin bitmaskingsverdi for å vise forholdet mellom en flis rekkefølge på flisarket og dens posisjon i spillet. 

For eksempel, la oss undersøke flisen i øvre høyre hjørne av figuren til høyre. Denne flisen er kantet av fliser til Vesten og Souh. Den boolske sjekken returnerer en binær verdi på 1010, eller 1 * 0 + 2 * 1 + 4 * 0 + 8 * 1 = 10. Denne verdien tilsvarer 11th sprite i flisarket.

Fliserett Kompleksitet

Antallet krevende retningsboolske kontroller avhenger av den tilsatte kompleksiteten til flisene. Ved å ignorere hjørnebrikker, kan du bruke denne forenklede 4-bits-løsningen som bare krever fire retningsbaserte binære kontroller. 

Men hva skjer når du vil skape mer visuelt tiltalende terreng? Du må håndtere eksistensen av hjørnefliser, noe som øker mengden sprites fra 16 til 48. Følgende 8-bits bitmasking-eksempel krever åtte boolske retningskontroller per fliser.

8-biters bitmasking med hjørnefliser

For dette eksempelet lager vi et topp-down-fliserett som viser gressletter i nærheten av havet. I dette tilfellet eksisterer vårt hav på et lag under terrengfliser. Dette gjør at vi kan bruke en terrengløsning, samtidig som vi opprettholder illusjonen om at to terrengtyper kolliderer. 

Når spillet kjører og bitmasking prosedyren er fullført, vil sprites aldri forandre seg. Dette er en sømløs, statisk implementering av bitmasking hvor alt foregår før spilleren noen gang ser flisene.

Vi presenterer hjørnefliser

Vi vil at terrenget skal være mer visuelt interessant enn den forrige 4-biters løsningen, slik at hjørnebrikker er påkrevd. Denne ekstra bitvis visuell kompleksitet krever en eksponentiell mengde ekstra arbeid for kunstneren, programmereren og selve spillet. Ved å utvide på det vi lærte av 4-bits-løsningen, kan vi raskt forstå hvordan vi nærmer oss 8-bits-løsningen.

Her er det komplette spritarket for våre terrengfliser på havet. Ser du noe merkelig på antall fliser? 4-bits eksempelet fra tidligere resulterte i 24 = 16 fliser, så dette 8-bits eksempelet bør sikkert resultere i 28 = 256 fliser, men det er klart færre enn det der. 

Selv om det er sant at denne 8-bits bitmaskingsprosedyren resulterer i 256 mulige binære verdier, krever ikke alle kombinasjoner en helt unik flis. Følgende eksempel vil bidra til å forklare hvordan 256 kombinasjoner kan representeres av bare 48 fliser.

8-biters retningsverdier

  • Nordvest = 20 = 1
  • Nord = 21 = 2
  • Nordøst = 22 = 4
  • Vest = 23 = 8
  • Øst = 24 = 16
  • Sørvest = 25 = 32
  • Sør = 26 = 64
  • Sørøst = 27 = 128

Nå lager vi åtte Boolske retningskontroller. Midtflisen ovenfor grenser til flisene til nord, nordøst og øst, så denne boolske sjekken returnerer en binær verdi av 00010110 eller 1 * 0 + 2 * 1 + 4 * 1 + 8 * 0 + 16 * 1 + 32 * 0 + 64 * 0 + 128 * 0 = 22.

Flisen til venstre over er lik den forrige flisen, men nå er den også grenset av fliser til Sørvest og Sørøst. Denne boolske retningskontrollen bør returnere en binær verdi på 10110110, eller 1 * 0 + 2 * 1 + 4 * 1 + 8 * 0 + 16 * 1 + 32 * 1 + 64 * 0 + 128 * 1 = 182

Denne verdien er forskjellig fra forrige fliser, men begge fliser vil faktisk være visuelt like, så det blir overflødig. 

For å eliminere redundansene legger vi til en ekstra betingelse for vår boolske retningskontroll: når du ser etter nærvær av grenser hjørne fliser, vi må også sjekke om nabobilder i de fire kardinalretningene (direkte nord, øst, sør eller vest). 

For eksempel er flisen til nord-øst naboen av eksisterende fliser, mens flisene til sør-vest og sør-øst ikke er. Dette betyr at sør-vest og sør-øst fliser ikke er inkludert i bitmasking beregningen. 

Med denne nye tilstanden returnerer denne boolske sjekken en binær verdi på 00010110 eller 1 * 0 + 2 * 1 + 4 * 1 + 8 * 0 + 16 * 1 + 32 * 0 + 64 * 0 + 128 * 0 = 22 akkurat som før. Nå kan du se hvordan 256 kombinasjonene kan representeres av bare 48 fliser.

Tile Order

Et annet problem du kanskje ser er at verdiene beregnet ved 8-bits bitmasking-prosedyren ikke lenger korrelerer til sekvensiell rekkefølge av fliser i sprite-arket. Det er bare 48 fliser, men våre mulige beregnede verdier varierer fra 0 til 255, slik at vi ikke lenger kan bruke den beregnede verdien som en direkte referanse når du tar den riktige sprite. 

Det vi trenger er derfor en datastruktur for å inneholde listen over beregnede verdier og tilhørende fliser. Hvordan du vil implementere dette, er opp til deg, men husk at bestillingen der du ser etter omkringliggende fliser dikterer rekkefølgen der flisene dine skal plasseres i spritearket. 

For dette eksempelet ser vi etter grensefliser i følgende rekkefølge: Nordvest, Nord, Nord-Øst, Vest, Øst, Sør-Vest, Sør, Sørøst. 

Nedenfor er det komplette settet av bitmaskingsverdier som de relaterer til flisposisjonene i vårt sprite-ark (gjerne bruke disse verdiene i prosjektet for å spare tid):

2 = 1, 8 = 2, 10 = 3, 11 = 4, 16 = 5, 18 = 6, 22 = 7, 24 = 8, 26 = 9, 27 = 10, 30 = 11, 31 = 12, 64 = 13, 66 = 14, 72 = 15, 74 = 16, 75 = 17, 80 = 18, 82 = 19, 86 = 20, 88 = 21, 90 = 22, 91 = 23, 94 = 24, 95 = 25 , 104 = 26, 106 = 27, 107 = 28, 120 = 29, 122 = 30, 123 = 31, 126 = 32, 127 = 33, 208 = 34, 210 = 35, 214 = 36, 216 = 37, 218 = 38, 219 = 39, 222 = 40, 223 = 41, 248 = 42, 250 = 43, 251 = 44, 254 = 45, 255 = 46, 0 = 47

Flere terrengtyper

Alle våre tidligere eksempler antar en enkelt terrengtype, men hva om vi introduserer et andre terreng til ligningen? Vi trenger en 5-bit bitmasking løsning, og vi må definere våre to terrengtyper. Vi må også tilordne en verdi til senterflisen som kun telles under spesielle forhold. Husk at vi ikke lenger regner med "tomt rom" som i de foregående eksemplene; fliser må nå omgis av en annen flis på alle sider.

Ovenstående figur viser et eksempel med to terrengtyper og ingen hjørnefliser. Type 1 alltidreturnerer en verdi på 0 når det oppdages under retningskontrollen; Midtflisverdien beregnes og brukes bare hvis det er terreng type 2. 

Midtflisen i eksempelet ovenfor er omgitt av terreng type 2 til nord, vest og øst og av terreng type 1 mot sør. Midtflisen er terreng type 1, så det regnes ikke. Denne boolske sjekken returnerer en binær verdi på  00111, eller  1 * 1 + 2 * 1 + 4 * 1 + 8 * 0 + 16 * 0 = 7.

I dette eksemplet er vårt senterflate terreng type 2, så det regnes i beregningen. Midtflisen er omgitt av terreng type 2 til nord og vest. Det er også omgitt av terreng type 1 mot øst og sør. Denne boolske sjekken returnerer en binær verdi på  10011, eller  1 * 1 + 2 * 1 + 4 * 0 + 8 * 0 + 16 * 1 = 19 .

Dynamisk implementering

Bitmaskingsberegningen kan også utføres under gameplay, noe som gjør det mulig for sanntidsendringer i fliseplassering og utseende. Dette er nyttig for ødeleggende terreng, samt spill som tillater utforming og bygging. Den første bitmaskingsprosedyren er obligatorisk for alle fliser, men eventuelle dynamiske beregninger skal bare utføres når det er absolutt nødvendig. For eksempel ville en ødelagt terrengfliser utløse bitmaskingsberegningen bare for omkringliggende fliser.

Konklusjon

Tile bitmasking er det perfekte eksempelet på å bygge et arbeidssystem for å hjelpe deg i spillutvikling. Det er ikke noe som direkte påvirker spillerens opplevelse; I stedet gir denne metoden for å automatisere en tidkrevende del av nivådesign en verdifull fordel for utvikleren. For å si det enkelt: flisemasking er en rask måte å gjøre spillet ditt skitne arbeid, slik at du kan fokusere på viktige oppgaver.