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

Hva du skal skape

I denne opplæringen konverterer vi et konvensjonelt 2D-fliserbasert Sokoban-spill til isometriske og sekskantede visninger. Hvis du er ny på isometriske eller sekskantede spill, kan det være overveldende først å prøve å følge dem begge samtidig. I så fall anbefaler jeg at du velger isometrisk først og deretter kommer tilbake senere for den sekskantede versjonen.

Vi skal bygge på toppen av den tidligere Unity-opplæringen: Unity 2D Tile-Based Sokoban Game. Vennligst gå gjennom opplæringen først, siden det meste av koden forblir uendret og alle kjernekonseptene forblir de samme. Jeg vil også koble til andre opplæringsprogrammer som forklarer noen av de underliggende konseptene.

Det viktigste aspektet ved å lage isometriske eller sekskantede versjoner fra en 2D-versjon, er å finne ut plasseringen av elementene. Vi vil bruke konverteringsmetoder basert på ligninger for å konvertere mellom de ulike koordinatsystemene.

Denne opplæringen har to seksjoner, en for isometrisk versjon og den andre for den sekskantede versjonen.

1. Isometrisk Sokoban Game

La oss dykke rett inn i isometrisk versjon når du har gått gjennom den opprinnelige opplæringen. Bildet under viser hvordan den isometriske versjonen ville se ut, forutsatt at vi bruker samme nivåinformasjon som brukes i den opprinnelige opplæringen.

Isometrisk visning

Isometrisk teori, konverteringsligning og implementering er forklart i flere opplæringsprogrammer på Envato Tuts +. En gammel Flash-basert forklaring finner du i denne detaljerte opplæringen. Jeg vil anbefale denne Phaser-baserte opplæringen som det er nyere og fremtidige bevis.

Selv om skriptspråkene som brukes i disse veiledningene, er henholdsvis ActionScript 3 og JavaScript, gjelder teorien overalt, uavhengig av programmeringsspråk. I det hele tatt koker det ned til disse konverteringsligningene som skal brukes til å konvertere 2D kartesiske koordinater til isometriske koordinater eller omvendt.

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

Vi vil bruke følgende Unity-funksjon for konvertering til isometriske koordinater.

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

Endringer i kunst

Vi bruker samme nivåinformasjon for å lage vårt 2D-array, levelData, som vil drive isometrisk representasjon. Det meste av koden vil også forbli det samme, annet enn det som er spesifikt for isometrisk visning.

Kunsten må imidlertid ha noen endringer i forhold til svingpunktene. Vennligst se bildet nedenfor og forklaringen som følger.

De IsometricSokoban game script bruker endrede sprites som heroSprite, ballSprite, og blockSprite. Bildet viser de nye pivotpoengene som brukes til disse sprites. Denne endringen gir det pseudo 3D-utseendet vi sikter mot med isometrisk visning. BlockSprite er en ny sprite som vi legger til når vi finner en invalidTile.

Det vil hjelpe meg å forklare det viktigste aspektet av isometriske spill, dybdsortering. Selv om sprite er bare en sekskant, vurderer vi det som en 3D-terning hvor pivoten ligger midt på undersiden av terningen.

Endringer i kode

Vennligst last ned koden som deles gjennom det koblede git-repository før du fortsetter videre. De CreateLevel Metoden har noen få endringer som omhandler skalaen og posisjonering av fliser og tillegg av blockTile. Skalaen til tileSprite, som bare er et diamantformat som representerer vår flis, må endres som nedenfor.

tile.transform.localScale = ny Vector2 (tileSize-1, (tileSize-1) / 2); // størrelse er kritisk for isometrisk form

Dette gjenspeiler at en isometrisk flis vil ha en høyde på halvparten av bredden. De heroSprite og ballSprite ha en størrelse på tileSize / 2.

hero.transform.localScale = Vector2.one * (tileSize / 2); // vi bruker halvparten av fliser for beboere

Uansett hvor vi finner en invalidTile, vi legger til en blockTile ved hjelp av følgende kode.

flis = ny GameObject ("blokk" + i.ToString () + "_" + j.ToString ()); // lage ny flis rootatree = Mathf.Sqrt (3); flyte newDimension = 2 * tileSize / rootThree; tile.transform.localScale = ny Vector2 (newDimension, tileSize); // vi må sette noen høyde sr = tile.AddComponent(); // legge til en sprite renderer sr.sprite = blockSprite; // tilordne blokksprite sr.sortingOrder = 1; // dette må også ha høyere sorteringsordre Farge c = Color.gray; c.a = 0.9f; sr.color = c; tile.transform.position = GetScreenPointFromLevelIndices (i, j); // plassere scenen basert på nivåindekser occupants.Add (fliser, ny Vector2 (i, j)); // lagre nivåindeksene i blokk i dikt

Heksekanten må skaleres forskjellig for å få isometrisk utseende. Dette blir ikke et problem når kunsten håndteres av kunstnere. Vi bruker en litt lavere alfa verdi til blockSprite slik at vi kan se gjennom det, noe som gjør at vi kan se dybdsorteringen riktig. Legg merke til at vi legger disse fliser til beboere ordboken, som vil bli brukt senere for dybdsortering.

Plasseringen av flisene er gjort ved bruk av GetScreenPointFromLevelIndices metode, som i sin tur bruker CartesianToIsometric konverteringsmetode forklart tidligere. De Y akse peker i motsatt retning for enhet, som må vurderes mens du legger til middleOffset å plassere nivået midt på skjermen.

Vector2 GetScreenPointFromLevelIndices (int rad, int col) // konvertere indekser til posisjonsverdier, kol bestemmer x og rad bestemme y Vector2 tempPt = CartesianToIsometric (ny Vector2 (col * tileSize / 2, row * tileSize / 2)); // fjernet '-' delen som aksekorreksjon kan skje etter coverion tempPt.x- = middleOffset.x; // vi bruker forskyvningen utenfor koordinatkonvertering for å justere nivået i skjermbildet mid tempPt.y * = - 1; // enhet og aksekorreksjon tempPt.y + = middleOffset.y; // vi bruker forskyvningen utenfor koordinatkonvertering for å justere nivået i skjermens midtre returhastighet; 

På slutten av CreateLevel metode samt i slutten av TryMoveHero metode, vi kaller DepthSort metode. Dybdesortering er det viktigste aspektet av en isometrisk implementering. I hovedsak bestemmer vi hvilke fliser som går bak eller foran andre fliser i nivået. De DepthSort Metoden er som vist nedenfor.

privat tomrom DepthSort () int deep = 1; SpriteRenderer sr; Vector2 pos = ny Vector2 (); for (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 = depth; // tilordne ny dybde dybde ++; // trinn dybde

Skjønnheten i en 2D-arraybasert implementering er at for riktig isometrisk dybdsortering trenger vi bare å tildele sekvensielt høyere dybde mens vi analyserer nivået i rekkefølge, ved hjelp av sekvensiell for sløyfer. Dette fungerer for vårt enkle nivå med bare et enkelt lag av bakken. Hvis vi hadde flere bakkenivåer i ulike høyder, kan dybdsorteringen bli komplisert.

Alt annet er det samme som 2D-implementeringen som ble forklart i den forrige opplæringen.

Fullført nivå

Du kan bruke de samme tastaturkontrollene til å spille spillet. Den eneste forskjellen er at helten ikke beveger seg vertikalt eller horisontalt, men isometrisk. Det ferdige nivået vil se ut som bildet nedenfor.

Sjekk ut hvordan dybdsorteringen er tydelig synlig med vår nye blockTiles.

Det var ikke vanskelig, var det? Jeg inviterer deg til å endre nivådata i tekstfilen for å prøve nye nivåer. Neste opp er den sekskantede versjonen, noe som er litt mer komplisert, og jeg vil råde deg til å ta en pause for å spille med isometrisk versjon før du fortsetter.

2. Sekskantet Sokoban Game

Den sekskantede versjonen av Sokoban-nivået vil se ut som bildet nedenfor.

Heksagonal visning

Vi bruker den horisontale tilpasningen for sekskantet rutenett for denne opplæringen. Teorien bak sekskantet implementering krever mye videre lesing. Vennligst referer til denne opplæringsserien for en grunnleggende forståelse. Teorien er implementert i hjelpeklassen HexHelperHorizontal, som finnes i utils mappe.

Sekskantet koordineringskonvertering

De HexagonalSokoban spillskript bruker bekvemmelighetsmetoder fra hjelpeklassen for koordinatkonverteringer og andre sekskantede funksjoner. Hjelperklassen HexHelperHorizontal vil bare fungere med et horisontalt justert sekskantet rutenett. Det inkluderer metoder for å konvertere koordinater mellom offset, aksial og cubic systemer.

Forskjellen koordinat er den samme 2D kartesiske koordinaten. Den inneholder også a getNeighbors metode som tar inn en aksial koordinat og returnerer a Liste med alle de seks naboene til den cellekoordinaten. Bestillingen av listen er med klokken, og starter med den nordøstlige naboens cellekoordinat.

Endringer i kontroller

Med et sekskantet rutenett har vi seks bevegelsesretninger i stedet for fire, da sekskanten har seks sider mens en firkant har fire. Så vi har seks tastaturtaster for å kontrollere bevegelsen av vår helt, som vist på bildet nedenfor.

Nøklene er arrangert i samme layout som et sekskantet rutenett hvis du vurderer tastaturnøkkelen S som midtcelle, med alle kontrolltastene som sine sekskantede naboer. Det bidrar til å redusere forvirringen med å kontrollere bevegelsen. Tilsvarende endringer i inngangskoden er som nedenfor.

private void ApplyUserInput () // Vi har 6 bevegelsesretninger styrt av e, d, x, z, a, w i en syklisk sekvens som starter med NE til NW hvis (Input.GetKeyUp (userInputKeys [0])) TryMoveHero (1); // øst annet hvis (Input.GetKeyUp (userInputKeys [2])) TryMoveHero (2) ; // south east annet hvis (Input.GetKeyUp (userInputKeys [3])) TryMoveHero (3); // south west annet hvis (Input.GetKeyUp (userInputKeys [4])) TryMoveHero (4); / / vest annet hvis (Input.GetKeyUp (userInputKeys [5])) TryMoveHero (5); // north west

Det er ingen endring i kunsten, og det er ingen svingendringer som er nødvendige.

Andre endringer i kode

Jeg vil forklare koden endres med hensyn til den opprinnelige 2D Sokoban opplæringen og ikke den isometriske versjonen ovenfor. Vennligst referer til den koblede kildekoden for denne opplæringen. Det mest interessante faktum er at nesten hele koden forblir den samme. De CreateLevel Metoden har bare en endring, som er den middleOffset beregningen.

middleOffset.x = cols * tileWidth + tileWidth * 0.5f; // dette er endret for sekskantet middleOffset.y = rader * tileSize * 3/4 ​​+ tileSize * 0.75f; // dette endres for isometrisk 

En stor forandring er åpenbart måten skjermkoordinatene finnes i GetScreenPointFromLevelIndices metode.

Vector2 GetScreenPointFromLevelIndices (int rad, int col) // konvertere indekser til posisjonsverdier, kol bestemmer x og rad bestemmer y Vector2 tempPt = ny Vector2 (rad, col); tempPt = HexHelperHorizontal.offsetToAxial (tempPt); // konverter fra offset til aksial // konverter aksialpunkt til skjermpunkt tempPt = HexHelperHorizontal.axialToScreen (tempPt, sideLength); tempPt.x- = middleOffset.x-Screen.width / 2; // legg til forskyvninger for midtjustering tempPt.y * = - 1; // enhet aksekorreksjon tempPt.y + = middleOffset.y-Screen.height / 2; retur tempPt; 

Her bruker vi hjelpeklassen til å først konvertere koordinaten til aksial og deretter finne den tilsvarende skjermkoordinaten. Vær oppmerksom på bruken av sidelength variabel for den andre konverteringen. Det er verdien av lengden på en side av sekskantflisen, som igjen er lik halvparten av avstanden mellom de to spisse endene av sekskanten. Derfor:

sidelength = tileSize * 0.5f;

Den eneste andre endringen er GetNextPositionAlong metode, som brukes av TryMoveHero metode for å finne den neste cellen i en gitt retning. Denne metoden er fullstendig endret for å imøtekomme den helt nye utformingen av nettet vårt.

privat Vector2 GetNextPositionAlong (Vector2 objPos, int retning) // Denne metoden er fullstendig endret for å imøtekomme den forskjellige måten naboer er funnet i sekskantet logikk objPos = HexHelperHorizontal.offsetToAxial (objPos); // konverter fra offset til aksial List naboer = HexHelperHorizontal.getNeighbors (objPos); objPos = naboer [retning]; // nabo listen følger samme rekkefølgen sekvens objPos = HexHelperHorizontal.axialToOffset (objPos); // konvertere tilbake fra aksial til offset return objPos; 

Ved hjelp av hjelperklassen kan vi enkelt returnere koordinatene til naboen i den angitte retningen.

Alt annet er det samme som den originale 2D-implementeringen. Det var ikke vanskelig, var det? Når det gjelder å forstå hvordan vi kom til konverteringsligningene ved å følge den sekskantede opplæringen, som er kjernepunktet i hele prosessen, er det ikke lett. Hvis du spiller og fullfører nivået, får du resultatet som under.

Konklusjon

Hovedelementet i begge konverteringene var koordinatkonverteringene. Den isometriske versjonen innebærer ytterligere endringer i kunsten med deres svingpunkt, samt behovet for dybdsortering.

Jeg tror at du har funnet ut hvor lett det er å lage nettbaserte spill ved hjelp av bare todimensjonale array-baserte nivådata og en flisbasert tilnærming. Det er ubegrensede muligheter og spill du kan skape med denne nye forståelsen.

Hvis du har forstått alle de konseptene vi har diskutert så langt, vil jeg invitere deg til å endre kontrollmetoden for å trykke og legge til noen banebetingelser. Lykke til.