Unity 2D Tile-Based Isometric och Hexagonal 'Sokoban' Game

Vad du ska skapa

I denna handledning konverterar vi ett konventionellt 2D-plattorbaserat Sokoban-spel i isometriska och sexkantiga vyer. Om du är ny på isometriska eller sexkantiga spel kan det vara överväldigande först att försöka följa dem båda samtidigt. I det fallet rekommenderar jag att du väljer isometrisk först och sedan kommer tillbaka senare i hexagonalversionen.

Vi kommer att bygga ovanpå den tidigare Unity-handledningen: Unity 2D Tile-Based Sokoban Game. Vänligen gå igenom handledningen först eftersom det mesta av koden förblir oförändrad och alla kärnbegrepp förblir desamma. Jag länkar också till andra handledning som förklarar några av de underliggande koncepten.

Den viktigaste aspekten när det gäller att skapa isometriska eller hexagonala versioner från en 2D-version är att bestämma placeringen av elementen. Vi kommer att använda omvandlingsmetoder baserade på ekvationer för att konvertera mellan de olika koordinatsystemen.

Denna handledning har två sektioner, en för isometrisk version och den andra för hexagonal versionen.

1. Isometrisk Sokoban Spel

Låt oss dyka rätt in i isometriska versionen när du har gått igenom den ursprungliga handledningen. Bilden nedan visar hur den isometriska versionen skulle se ut, förutsatt att vi använder samma nivåinformation som används i originalhandledningen.

Isometrisk vy

Isometrisk teori, omvandlingsekvation och implementering förklaras i flera handledning på Envato Tuts +. En gammal Flash-baserad förklaring finns i denna detaljerade handledning. Jag skulle rekommendera denna Phaser-baserade handledning som det är nyare och framtida bevis.

Även om skriptspråk som används i dessa handledningar är respektive ActionScript 3 respektive JavaScript, är teorin tillämplig överallt, oberoende av programmeringsspråk. I huvudsak koka det ner till dessa omvandlingsekvationer som ska användas för att konvertera 2D-kartesiska koordinater till isometriska koordinater eller vice versa.

// Cartesian till isometrisk: isoX = cartX - cartY; isoY = (cartX + cartY) / 2; // Isometrisk till Cartesian: cartX = (2 * isoY + isoX) / 2; cartY = (2 * isoY-isoX) / 2;

Vi kommer att använda följande Unity-funktion för omvandling till isometriska koordinater.

Vector2 CartesianToIsometric (Vector2 cartPt) Vector2 tempPt = ny vektor2 (); tempPt.x = cartPt.x-cartPt.y; tempPt.y = (cartPt.x + cartPt.y) / 2; retur (tempPt); 

Förändringar i konst

Vi använder samma nivåinformation för att skapa vår 2D-array, levelData, som kommer att driva isometrisk representation. Huvuddelen av koden kommer också att förbli densamma, annan än den som är specifik för isometrisk vy.

Konsten måste emellertid ha vissa förändringar i förhållande till svängpunkterna. Vänligen se bilden nedan och förklaringen som följer.

De IsometricSokoban spelet script använder ändrade sprites som heroSprite, ballSprite, och blockSprite. Bilden visar de nya pivotpunkterna som används för dessa sprites. Denna förändring ger den pseudo 3D-look vi syftar till med isometrisk vy. BlockSprite är en ny sprite som vi lägger till när vi hittar en invalidTile.

Det hjälper mig att förklara den viktigaste aspekten av isometriska spel, djup sortering. Även om sprite är bara en hexagon, funderar vi på det som en 3D-kub där pivoten ligger i mitten av kubens bottenyta.

Ändringar i kod

Vänligen ladda ner koden som delas genom det länkade gitförvaret innan du fortsätter vidare. De CreateLevel Metoden har några förändringar som handlar om skalans storlek och positionering och tillägget av blockTile. Skala av tileSprite, som bara är en diamantformad bild som representerar vår markplatta, behöver ändras enligt nedan.

tile.transform.localScale = ny Vector2 (tegelSize-1, (tileSize-1) / 2); // storlek är kritisk för isometrisk form

Detta återspeglar det faktum att en isometrisk kakel kommer att ha en höjd på hälften av dess bredd. De heroSprite och den ballSprite ha en storlek på tileSize / 2.

hero.transform.localScale = Vector2.one * (tileSize / 2); // vi använder hälften av kakel för passagerare

Varhelst vi hittar en invalidTile, vi lägger till en blockTile med hjälp av följande kod.

kakel = nytt GameObject ("block" + i.ToString () + "_" + j.ToString ()); // skapa ny kakelflotta rootThree = Mathf.Sqrt (3); float newDimension = 2 * tileSize / rootThree; tile.transform.localScale = ny Vector2 (newDimension, tileSize); // vi måste ställa in någon höjd sr = tile.AddComponent(); // Lägg till en sprite renderer sr.sprite = blockSprite; // tilldela block sprite sr.sortingOrder = 1; // detta måste också ha högre sorteringsordning Färg c = Color.gray; C.A = 0.9f; sr.color = c; tile.transform.position = GetScreenPointFromLevelIndices (i, j); // placera i scen baserat på nivåindikatorer occupants.Add (kakel, ny Vector2 (i, j)); // lagra nivåindexen i block i dict

Sexkanten måste skalas olika för att få isometrisk utseende. Det här är inte ett problem när konsten hanteras av artister. Vi tillämpar ett något lägre alfavärde till blockSprite så att vi kan se igenom det, vilket gör att vi kan se djup sorteringen ordentligt. Observera att vi lägger till dessa plattor till åkande ordbok, som kommer att användas senare för djup sortering.

Placeringen av plattorna görs med hjälp av GetScreenPointFromLevelIndices metod som i sin tur använder CartesianToIsometric konvertering metod som förklaras tidigare. De Y axeln pekar i motsatt riktning för enhet, som måste beaktas när man lägger till middleOffset för att placera nivån mitt på skärmen.

Vector2 GetScreenPointFromLevelIndices (int rad, int col) // konvertera index till positionsvärden, kol bestämmer x och rad bestämmer y Vector2 tempPt = CartesianToIsometric (ny Vector2 (col * tileSize / 2, row * tileSize / 2)); '-' i delen y som axelkorrigering kan inträffa efter coverion tempPt.x- = middleOffset.x; // vi tillämpar offset utanför koordinatomvandlingen för att justera nivån i skärm mittempPt.y * = - 1; // enhet y axelkorrigering tempPt.y + = middleOffset.y; // vi tillämpar förskjutningen utanför koordinatomvandlingen för att justera nivån i skärmens mellanslags-tempPt; 

I slutet av CreateLevel metod samt i slutet av TryMoveHero metod vi kallar DepthSort metod. Djupsortering är den viktigaste aspekten av en isometrisk implementering. I huvudsak bestämmer vi vilka plattor som går bakom eller framför andra plattor i nivån. De DepthSort Metoden är som visas nedan.

privat tomrum DepthSort () int djup = 1; SpriteRenderer sr; Vector2 pos = ny Vector2 (); för (int i = 0; i < rows; i++)  for (int j = 0; j < cols; j++)  int val=levelData[i,j]; if(val!=groundTile && val!=destinationTile)//a tile which needs depth sorting pos.x=i; pos.y=j; GameObject occupant=GetOccupantAtPosition(pos);//find the occupant at this position if(occupant==null)Debug.Log("no occupant"); sr=occupant.GetComponent(); sr.sortingOrder = djup; // tilldela nytt djupdjup ++; // stegdjup

Skönheten i en 2D-arraybaserad implementering är att för den korrekta isometriska djupsorteringen behöver vi bara tilldela successivt högre djup medan vi analyserar nivån i ordning med hjälp av sekventiell för slingor. Detta fungerar för vår enkla nivå med endast ett enda lager av mark. Om vi ​​hade flera marknivåer vid olika höjder, skulle djup sorteringen bli komplicerad.

Allting förblir detsamma som 2D-implementeringen som förklaras i föregående handledning.

Slutförd nivå

Du kan använda samma tangentbordskontroller för att spela spelet. Den enda skillnaden är att hjälten inte kommer att röra sig vertikalt eller horisontellt men isometriskt. Den färdiga nivån skulle se ut som bilden nedan.

Kolla in hur djup sorteringen är tydlig synlig med vårt nya blockTiles.

Det var inte svårt, var det? Jag uppmanar dig att ändra nivådata i textfilen för att prova nya nivåer. Nästa upp är den hexagonala versionen, vilket är lite mer komplicerat, och jag rekommenderar dig att ta en paus att spela med isometrisk version innan du fortsätter.

2. Sexkantigt Sokoban-spel

Den sexkantiga versionen av Sokoban-nivån skulle se ut som bilden nedan.

Sexkantig vy

Vi använder den horisontella inriktningen för hexagonalt rutnät för denna handledning. Teorin bakom hexagonal implementering kräver en hel del ytterligare läsning. Vänligen hänvisa till denna handledningsserie för en grundläggande förståelse. Teorin implementeras i hjälparklassen HexHelperHorizontal, som finns i utils mapp.

Sexkantig koordineringskonvertering

De HexagonalSokoban spelskript använder bekvämlighetsmetoder från hjälparklassen för koordinatomvandlingar och andra hexagonala funktioner. Hjälperklassen HexHelperHorizontal kommer bara att fungera med ett horisontellt inriktat sexkantigt rutnät. Det innehåller metoder för att konvertera koordinater mellan förskjutna, axiella och kubiska system.

Förskjutningskoordinaten är samma 2D-kartesiska koordinat. Det innehåller också en getNeighbors metod som tar in en axiell koordinat och returnerar a Lista med alla de sex grannarna i den cellkoordinaten. Listans ordning är medurs, från och med den nordöstra grannens cellkoordinat.

Ändringar i kontroller

Med ett sexkantigt rutnät har vi sex rörelseriktningar istället för fyra, eftersom hexagonen har sex sidor medan en fyrkant har fyra. Så vi har sex tangentbordstangenter för att styra vår hjälte, enligt bilden nedan.

Nycklarna är ordnade i samma layout som ett hexagonalt rutnät om du anser tangentbordsknappen S som mittencellen, med alla kontrollnycklarna som dess hexagonala grannar. Det hjälper till att minska förvirringen med att styra rörelsen. Motsvarande ändringar i inmatningskoden är som nedan.

privat void ApplyUserInput () // Vi har 6 riktningsriktningar kontrollerade av e, d, x, z, a, w i en cyklisk sekvens som börjar med NE till NW om (Input.GetKeyUp (userInputKeys [0])) TryMoveHero (1); // öst annars om (Input.GetKeyUp (userInputKeys [2])) TryMoveHero (2) ; // south east annars om (Input.GetKeyUp (userInputKeys [3])) TryMoveHero (3); // south west annars om (Input.GetKeyUp (userInputKeys [4])) TryMoveHero (4); / / west annars om (Input.GetKeyUp (userInputKeys [5])) TryMoveHero (5); // north west

Det finns ingen förändring i konsten, och det behövs inga pivotändringar.

Andra ändringar i kod

Jag kommer att förklara koden ändras med avseende på den ursprungliga 2D Sokoban handledningen och inte den isometriska versionen ovan. Vänligen hänvisa till länkad källkod för denna handledning. Det mest intressanta faktumet är att nästan hela koden förblir densamma. De CreateLevel Metoden har bara en förändring, vilket är middleOffset beräkning.

middleOffset.x = cols * tileWidth + tileWidth * 0.5f; // detta ändras för hexagonala middleOffset.y = rader * tileSize * 3/4 ​​+ tileSize * 0.75f; // detta ändras för isometrisk 

En stor förändring är uppenbarligen hur skärmkoordinaterna finns i GetScreenPointFromLevelIndices metod.

Vector2 GetScreenPointFromLevelIndices (int rad, int col) // konvertera index till positionsvärden, kol bestämmer x & rad bestäm y Vector2 tempPt = ny Vector2 (rad, col); tempPt = HexHelperHorizontal.offsetToAxial (tempPt); // konvertera från offset till axial // konvertera axiell punkt till skärmpunkt tempPt = HexHelperHorizontal.axialToScreen (tempPt, sidLength); tempPt.x- = middleOffset.x-Screen.width / 2; // lägg till förskjutningar för mittenjustering tempPt.y * = - 1; // enhet-axelkorrigering tempPt.y + = middleOffset.y-Screen.height / 2; returnera tempPt; 

Här använder vi hjälparklassen för att först konvertera koordinaten till axiell och hitta motsvarande skärmkoordinat. Observera användningen av sidolängd variabel för den andra omvandlingen. Det är värdet av längden på en sida av hexagonplattan, som återigen är lika med hälften av avståndet mellan sexkantens två punktiga ändar. Därav:

sideLength = tileSize * 0.5f;

Den enda andra ändringen är GetNextPositionAlong metod som används av TryMoveHero metod för att hitta nästa cell i en given riktning. Den här metoden är helt ändrad för att rymma den helt nya layouten i vårt nät.

privat Vector2 GetNextPositionAlong (Vector2 objPos, int riktning) // den här metoden är helt ändrad för att tillgodose de olika sätt som grannar finns i hexagonal logik objPos = HexHelperHorizontal.offsetToAxial (objPos); // konvertera från förskjutning till axiell lista grannar = HexHelperHorizontal.getNeighbors (objPos); objPos = grannar [riktning]; // grannlistan följer samma ordningsföljd objPos = HexHelperHorizontal.axialToOffset (objPos); // konvertera tillbaka från axiell till offset return objPos; 

Med hjälp av hjälparklassen kan vi enkelt återvända grannarnas koordinater i den givna riktningen.

Allt annat är detsamma som den ursprungliga 2D-implementeringen. Det var inte svårt, var det? Det är inte lätt att förstå hur vi kom fram till omvandlingsekvationerna genom att följa den sexkantiga handledningen, som är kärnan i hela processen. Om du spelar och fullbordar nivån får du resultatet enligt nedan.

Slutsats

Huvudelementet i båda konverteringarna var koordinatomvandlingarna. Den isometriska versionen innefattar ytterligare förändringar inom tekniken med sin svängpunkt samt behovet av djupsortering.

Jag tror att du har funnit hur lätt det är att skapa nätbaserade spel med bara tvådimensionell arraybaserad nivådata och en kakelbaserad metod. Det finns obegränsade möjligheter och spel som du kan skapa med denna nya förståelse.

Om du har förstått alla begrepp som vi har diskuterat hittills skulle jag bjuda in dig att ändra kontrollmetoden för att knacka på och lägga till några sökningar. Lycka till.