Så här använder du Tile Bitmasking för att automatisera dina nivålayouts

Att skapa en visuellt tilltalande och varierad kakel är en tidskrävande process, men resultaten är ofta värda det. Men även efter att du skapat konsten måste du fortfarande styra allt tillsammans inom din nivå! 

Du kan placera varje kakel, en efter en, för hand, eller du kan automatisera processen genom att använda bitmasking, så du behöver bara rita terrängens form.

Vad är Tile Bitmasking?

Tile bitmasking är en metod för att automatiskt välja rätt sprite från en definierad kakel. Detta gör att du kan placera en generisk platshållare kakel överallt där du vill att en viss typ av terräng ska visas istället för att man placerar ett potentiellt enormt urval av olika plattor. 

Se den här videon för en demonstration:

(Du kan ladda ner demos och källfiler från GitHub repo.)

När det gäller flera typer av terräng kan antalet olika variationer överstiga 300 eller fler plattor. Att rita dessa många olika sprites är definitivt en tidskrävande process, men kakelbitmaskning säkerställer att handlingen med att placera dessa plattor är snabb och effektiv.

Med ett statiskt genomförande av bitmasking genereras kartor vid körning. Med några små tweaks kan du expandera bitmaskering för att möjliggöra dynamiska plattor som förändras under spelets gång. I denna handledning kommer vi att täcka grunderna för kakelbitmaskning medan vi arbetar mot mer komplicerade implementeringar som använder hörnplattor och flera terrängtyper.

Hur kakelbitmaskning fungerar

Översikt

Tile bitmasking handlar om att beräkna ett numeriskt värde och tilldela ett specifikt sprite baserat på det värdet. Varje kakel ser på sina närliggande kakel för att bestämma vilken sprite från uppsättningen att tilldela sig själv. 

Varje sprite i en kakel är numrerad, och bitmaskningsprocessen returnerar ett tal som motsvarar positionen för en sprite i kakelstenen. Vid körning utförs bitmaskningsförfarandet, och varje sida är uppdaterad med lämplig sprite.

Sprite-arket ovan består av terrängplattor med alla möjliga gränskonfigurationer. Antalet på varje kakel representerar bitmaskningsvärdet, vilket vi kommer att lära oss att beräkna i nästa avsnitt. För nu är det viktigt att förstå hur bitmaskningsvärdet avser terrängsplattformen. De sprites beställs i följd så att ett bitmaskingsvärde på 0 returnerar den första sprite, hela vägen till ett värde av 15 som återvänder 16th sprite. 

Beräkning av bitmaskningsvärdet

Beräkning av detta värde är relativt enkelt. I det här exemplet antar vi en enda terrängtyp utan hörnbitar. 

Varje kakel kontrollerar förekomsten av plattor till norr, väst, öst och syd, och varje kontroll returnerar en booleska, där 0 representerar ett tomt utrymme och 1 betecknar närvaron av en annan terrängplatta. 

Detta booleska resultatet multipliceras sedan med det binära riktvärdet och läggs till det totala bitmaskningsvärdet, det är lättare att förstå med några exempel:

4-bitars riktvärden

  • Nord = 20 = 1
  • Väst = 21 = 2
  • Öst = 22 = 4
  • Söder = 23 = 8

Den gröna torget i figuren ovan representerar terrängplattan vi beräknar. Vi börjar med att leta efter en kakel till norr. Det finns ingen kakel i norr, så den booleska kontrollen returnerar ett värde av 0. Vi multiplicerar 0 med riktvärdet för norr, 20 = 1, ger oss 1 * 0 = 0

För en terrängplatta omgiven helt av tomt utrymme, returnerar varje boolsk kontroll 0, vilket resulterar i 4-bitars binärt tal 0000 eller 1 * 0 + 2 * 0 + 4 * 0 + 8 * 0 = 0. Det finns 16 totalt möjliga kombinationer, från 0 till 15, så den 1st Sprite i kakelbenet kommer att användas för att representera denna typ av terrängplatta med ett värde av 0.

En terrängplatta gränsad av en enda kakel till norr återvänder ett binärt värde av 0001, eller 1 * 1 + 2 * 0 + 4 * 0 + 8 * 0 = 1. Den 2nd Sprite i kakelbenet kommer att användas för att representera denna typ av terräng med ett värde av 1.

En terrängplatta som gränsas av en kakel till norr och en kakel till öster ger ett binärt värde av 0101, eller 1 * 1 + 2 * 0 + 4 * 1 + 8 * 0 = 5. Den 6: e sprite i kakelbenet kommer att användas för att representera denna typ av terräng med ett värde av 5.

En terrängplatta gränsad av en kakel till öst och en kakel till väst återvänder ett binärt värde av 0110, eller 1 * 0 + 2 * 1 + 4 * 1 + 8 * 0 = 6. Den 7: e sprite i kakelbenet kommer att användas för att representera denna typ av terräng med ett värde av 6.

Tilldela Sprites till plattor

Efter att ha beräknat ett flisens bitmaskningsvärde, tilldelar vi den lämpliga spritet från plattan. Det här sista steget kan utföras i realtid när kartan laddas, eller resultatet kan sparas och laddas i din kakeleditor för valet för vidare redigering.

Figuren till vänster representerar en 4-bitars en-terrängs kakel som det skulle ses i följd på ett kakelplatta. Figuren till höger visar hur kakel ser ut i spelet efter att de placerats med hjälp av bitmaskningsproceduren. Varje kakel markeras med sitt bitmaskningsvärde för att visa förhållandet mellan en kakel ordning på kakelplattan och dess position i spelet. 

Låt oss undersöka plattan i det övre högra hörnet av figuren till höger. Denna kakel är kantad av kakel till väst och souh. Den booleska kontrollen returnerar ett binärt värde på 1010, eller 1 * 0 + 2 * 1 + 4 * 0 + 8 * 1 = 10. Detta värde motsvarar 11th sprite i kakelplattan.

Tileset-komplexitet

Antalet riktiga booleska kontroller beror på den avsedda komplexiteten hos din tileset. Genom att ignorera hörnstycken kan du använda denna förenklade 4-bitars lösning som bara kräver fyra riktiga binära kontroller. 

Men vad händer när du vill skapa mer visuellt tilltalande terräng? Du kommer att behöva hantera förekomsten av hörnplattor, vilket ökar mängden sprites från 16 till 48. Följande 8-bitars bitmaskingsexempel kräver åtta boolska riktningskontroller per kakel.

8-bitars bitmaskning med hörnplattor

I det här exemplet skapar vi en topp-down kakel som visar gräsbevuxen terräng nära havet. I det här fallet finns vårt hav på ett lager under terrängplattorna. Detta gör att vi kan använda en enda terränglösning, samtidigt som vi behåller illusionen att två terrängtyper kolliderar. 

När spelet är igång och bitmaskningsproceduren är klar kommer sprites aldrig att förändras. Detta är ett sömlöst, statiskt genomförande av bitmaskering där allting äger rum innan spelaren någonsin ser plattorna.

Introduktion av hörnplattor

Vi vill att terrängen ska vara mer visuellt intressant än den tidigare 4-bitarslösningen, så hörnstycken krävs. Denna extra bit av visuell komplexitet kräver en exponentiell mängd extra arbete för artisten, programmeraren och själva spelet. Genom att utöka vad vi lärde oss från 4-bitarslösningen kan vi snabbt förstå hur vi ska närma oss 8-bitarslösningen.

Här är det kompletta spritarket för våra havsytor. Märker du någonting märkligt om antalet kakel? 4-bitarsexemplet från tidigare resulterade i 24 = 16 plattor, så det här 8-bitarsexemplet borde säkert resultera i 28 = 256 plattor, men det finns klart färre än det där. 

Även om det är sant att detta 8-bitars bitmaskningsförfarande resulterar i 256 möjliga binära värden, kräver inte varje kombination en helt unik kakel. Följande exempel hjälper till att förklara hur 256 kombinationer kan representeras av endast 48 plattor.

8-bitars riktvärden

  • Nordväst = 20 = 1
  • Nord = 21 = 2
  • Nordöstra = 22 = 4
  • Väst = 23 = 8
  • Öst = 24 = 16
  • South West = 25 = 32
  • Söder = 26 = 64
  • South East = 27 = 128

Nu gör vi åtta Boolean riktningskontroller. Centrumplattan ovan gränsas av kakel till norr, nordost och öst, så den här booleska kontrollen returnerar ett binärt värde av 00010110 eller 1 * 0 + 2 * 1 + 4 * 1 + 8 * 0 + 16 * 1 + 32 * 0 + 64 * 0 + 128 * 0 = 22.

Kakel till vänster ovan liknar tidigare kakel, men nu är den också gränsad till kakel till sydvästra och sydost. Denna booleska riktningskontroll skall returnera ett binärt värde av 10110110, eller 1 * 0 + 2 * 1 + 4 * 1 + 8 * 0 + 16 * 1 + 32 * 1 + 64 * 0 + 128 * 1 = 182

Detta värde skiljer sig från tidigare kakel, men båda kakelarna skulle faktiskt vara identiska, så det blir överflödigt. 

För att eliminera uppsägningarna lägger vi till ett extra villkor för vår booleska riktningskontroll: vid kontroll av närvaron av gränsar hörn kakel, vi måste också kolla efter angränsande plattor i de fyra kardinalriktningarna (direkt norr, öst, syd eller väst). 

Till exempel gränsar till nordöstra grannar med befintliga plattor, medan kakel till sydvästra och sydost inte är. Det betyder att syd-väst och sydost kakel inte ingår i bitmasking beräkningen. 

Med detta nya villkor returnerar den booleska kontrollen ett binärt värde på 00010110 eller 1 * 0 + 2 * 1 + 4 * 1 + 8 * 0 + 16 * 1 + 32 * 0 + 64 * 0 + 128 * 0 = 22 precis som tidigare. Nu kan du se hur 256 kombinationer kan representeras av endast 48 plattor.

Tile Order

Ett annat problem som du kanske märker är att värdena beräknade med 8-bitars bitmaskningsproceduren inte längre korrelerar med sekvensiell ordning för plattorna i spritarket. Det finns bara 48 plattor, men våra möjliga beräknade värden varierar från 0 till 255, så vi kan inte längre använda det beräknade värdet som en direkt referens när vi tar den lämpliga spriten. 

Vad vi behöver är därför en datastruktur som innehåller listan över beräknade värden och deras motsvarande kakelvärden. Hur du vill genomföra detta är upp till dig, men kom ihåg att den ordning i vilken du söker efter omgivande plattor dikterar den ordning i vilken dina plattor ska placeras i spritarket. 

För detta exempel kontrollerar vi för gränsande plattor i följande ordning: Nordväst, Nord, Nordöst, Väst, Öst, Sydväst, Syd, Sydost. 

Nedan är den kompletta uppsättningen bitmaskningsvärden som de relaterar till positionerna av plattor i vårt sprite-ark (gärna använda dessa värden i ditt projekt för att spara 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

Flera terrängtyper

Alla våra tidigare exempel antar en enda terrängtyp, men vad händer om vi introducerar en andra terräng till ekvationen? Vi behöver en 5-bitars bitmasking-lösning, och vi måste definiera våra två terrängtyper. Vi måste också tilldela ett värde till mittenplattan som bara räknas under specifika förhållanden. Kom ihåg att vi inte längre redovisar "tomt utrymme" som i de tidigare exemplen; kakel måste nu omges av en annan kakel på alla sidor.

Ovanstående figur visar ett exempel med två terrängtyper och inga hörnplattor. Typ 1 alltidreturnerar ett värde av 0 när det detekteras under riktningskontrollen; mittvärdet beräknas och används endast om det är terräng typ 2. 

Centrumplattan i ovanstående exempel är omgiven av terräng typ 2 till norr, väst och öst, och av terräng typ 1 mot syd. Centrumplattan är terräng typ 1, så det räknas inte. Den här booleska kontrollen returnerar ett binärt värde av  00111, eller  1 * 1 + 2 * 1 + 4 * 1 + 8 * 0 + 16 * O = 7.

I det här exemplet är vår mittkakel terräng typ 2, så det räknas i beräkningen. Centrumplattan är omgiven av terräng typ 2 till norr och väst. Det är också omgivet av terräng typ 1 mot öst och syd. Den här booleska kontrollen returnerar ett binärt värde av  10011, eller  1 * 1 + 2 * 1 + 4 * 0 + 8 * 0 + 16 * 1 = 19 .

Dynamisk implementering

Bitmaskingsberäkningen kan också utföras under spel, vilket möjliggör för ändringar i realtid i kakelplacering och utseende. Detta är användbart för destruerbar terräng samt spel som möjliggör konstruktion och byggnad. Det initiala bitmaskningsförfarandet är obligatoriskt för alla plattor, men ytterligare dynamiska beräkningar bör endast utföras när det är absolut nödvändigt. Till exempel skulle en förstörd terrängplatta utlösa bitmaskningsberäkning endast för omgivande plattor.

Slutsats

Tile bitmasking är det perfekta exemplet på att bygga ett arbetssystem för att hjälpa dig i spelutveckling. Det är inte något som direkt påverkar spelarens upplevelse. I stället ger denna metod att automatisera en tidskrävande del av nivådesign en värdefull fördel för utvecklaren. För att uttrycka det enkelt: kakelbitmaskning är ett snabbt sätt att göra spelet ditt smutsiga arbete, så att du kan fokusera på viktigare uppgifter.