Unity 2D tegel-gebaseerde isometrische en zeshoekige 'Sokoban' game

Wat je gaat creëren

In deze tutorial zullen we een conventioneel 2D-tegelgebaseerd Sokoban-spel omzetten in isometrische en hexagonale weergaven. Als u isometrische of zeshoekige spellen nog niet kent, kan het aanvankelijk overweldigend zijn om beide tegelijkertijd te proberen. In dat geval raad ik aan eerst isometrisch te kiezen en dan later terug te komen voor de zeshoekige versie.

We zullen bovenop de eerdere Unity-tutorial bouwen: Unity 2D Tile-based Sokoban Game. Ga eerst eerst door de tutorial aangezien het grootste deel van de code ongewijzigd blijft en alle kernconcepten hetzelfde blijven. Ik zal ook een link plaatsen naar andere tutorials waarin enkele van de onderliggende concepten worden toegelicht.

Het belangrijkste aspect bij het maken van isometrische of zeshoekige versies van een 2D-versie is het uitzoeken van de positionering van de elementen. We gebruiken conversiemethoden op basis van vergelijkingen om de verschillende coördinatensystemen om te zetten.

Deze zelfstudie bestaat uit twee delen, een voor de isometrische versie en de andere voor de zeshoekige versie.

1. Isometrisch Sokoban-spel

Laten we meteen naar de isometrische versie duiken nadat je de originele tutorial hebt doorlopen. De onderstaande afbeelding laat zien hoe de isometrische versie eruit zou zien, op voorwaarde dat we dezelfde niveau-informatie gebruiken als gebruikt in de originele zelfstudie.

Isometrische weergave

Isometrische theorie, conversievergelijking en implementatie worden uitgelegd in meerdere tutorials over Envato Tuts +. Een oude op Flash gebaseerde uitleg is te vinden in deze gedetailleerde tutorial. Ik zou deze op Phaser gebaseerde tutorial aanraden omdat deze recenter en toekomstbestendig is.

Hoewel de scripttalen die in deze zelfstudies worden gebruikt respectievelijk ActionScript 3 en JavaScript zijn, is de theorie overal toepasbaar, ongeacht de programmeertalen. In essentie komt het neer op deze conversievergelijkingen die moeten worden gebruikt om 2D Cartesiaanse coördinaten in isometrische coördinaten om te zetten of omgekeerd.

// Cartesisch naar isometrisch: isoX = cartX - cartY; isoY = (cartX + cartY) / 2; // Isometrisch naar Cartesisch: cartX = (2 * isoY + isoX) / 2; cartY = (2 * isoY - isoX) / 2;

We zullen de volgende Unity-functie gebruiken voor de conversie naar isometrische coördinaten.

Vector2 CartesianToIsometric (Vector2 cartPt) Vector2 tempPt = new Vector2 (); tempPt.x = cartPt.x-cartPt.y; tempPt.y = (cartPt.x cartPt.y +) / 2; terugkeer (tempPt); 

Veranderingen in Art

We zullen dezelfde niveau-informatie gebruiken om onze 2D-array te maken, levelData, welke de isometrische weergave zal aansturen. Het grootste deel van de code zal ook hetzelfde blijven, anders dan specifiek voor de isometrische weergave.

De kunst moet echter enkele veranderingen ondergaan met betrekking tot de draaipunten. Raadpleeg de afbeelding hieronder en de uitleg die volgt.

De IsometricSokoban spelscript gebruikt gemodificeerde sprites als heroSprite, ballSprite, en blockSprite. De afbeelding toont de nieuwe draaipunten die voor deze sprites worden gebruikt. Deze verandering geeft de pseudo-3D-look waar we naar streven met de isometrische weergave. De blockSprite is een nieuwe sprite die we toevoegen wanneer we een vinden invalidTile.

Het zal me helpen het belangrijkste aspect van isometrische spellen, dieptesortering, uit te leggen. Hoewel de sprite slechts een zeshoek is, beschouwen we het als een 3D-kubus waarin de spil zich in het midden van de onderkant van de kubus bevindt.

Veranderingen in de code

Download de code die wordt gedeeld via de gekoppelde git-repository voordat je verder gaat. De CreateLevel methode heeft een paar wijzigingen die betrekking hebben op de schaal en de positionering van de tegels en de toevoeging van de blockTile. De schaal van de tileSprite, dat is gewoon een afbeelding in diamantvorm die onze grondtegel weergeeft, moet worden gewijzigd zoals hieronder.

tile.transform.localScale = new Vector2 (tileSize-1, (tileSize-1) / 2); // size is essentieel voor isometrische vorm

Dit weerspiegelt het feit dat een isometrische tegel een hoogte van de helft van zijn breedte zal hebben. De heroSprite en de ballSprite hebben een grootte van tileSize / 2.

hero.transform.localScale = Vector2.one * (tileSize / 2); // we gebruiken de helft van de tilesize voor inzittenden

Waar we ook vinden invalidTile, we voegen een toe blockTile met behulp van de volgende code.

tile = new GameObject ("block" + i.ToString () + "_" + j.ToString ()); // create new tile float rootThree = Mathf.Sqrt (3); float newDimension = 2 * tileSize / rootThree; tile.transform.localScale = new Vector2 (newDimension, tileSize); // we moeten wat hoogte instellen sr = tile.AddComponent(); // voeg een sprite-renderer toe sr.sprite = blockSprite; // wijs block sprite sr.sortingOrder = 1; // dit moet ook een hogere sorteervolgorde hebben Kleur c = Color.gray; C.A. = 0.9f; sr.color = c; tile.transform.position = GetScreenPointFromLevelIndices (i, j); // plaats in scene op basis van bewoners van niveau-indices. Toevoegen (tile, new Vector2 (i, j)) // berg de niveau-indexen van blok op in dict

De zeshoek moet anders worden geschaald om de isometrische look te krijgen. Dit zal geen probleem zijn wanneer de kunst wordt behandeld door kunstenaars. We passen een iets lagere alpha-waarde toe op de blockSprite zodat we erdoorheen kunnen kijken, waardoor we de dieptesortering goed kunnen zien. Merk op dat we deze tegels toevoegen aan de inzittenden ook een woordenboek, dat later zal worden gebruikt voor dieptesortering.

De plaatsing van de tegels gebeurt met behulp van de GetScreenPointFromLevelIndices methode, die op zijn beurt de CartesianToIsometric conversiemethode eerder uitgelegd. De Y as wijst in de tegenovergestelde richting voor Unity, waarmee rekening moet worden gehouden bij het toevoegen van de middleOffset om het niveau in het midden van het scherm te plaatsen.

Vector2 GetScreenPointFromLevelIndices (int row, int col) // indices naar positiewaarden converteren, col bepaalt x & rij bepaalt y Vector2 tempPt = CartesianToIsometric (nieuwe Vector2 (col * tileSize / 2, row * tileSize / 2)) // verwijderd het onderdeel '-' in het deel als ascorrectie kan optreden na dekking tempPt.x- = middleOffset.x; // we passen de offset buiten de coördinaatomzetting toe om het niveau in te stellen in het scherm middle tempPt.y * = - 1; // eenheid y-as correctie tempPt.y + = middleOffset.y; // we passen de offset toe buiten de coördinaatconversie om het niveau in te stellen in het scherm middle return tempPt; 

Aan het einde van de CreateLevel zowel als aan het einde van de TryMoveHero methode, noemen we de DepthSort methode. Diepsortering is het belangrijkste aspect van een isometrische implementatie. In wezen bepalen we welke tegels achter of voor andere tegels in het niveau gaan. De DepthSort methode is zoals hieronder getoond.

private void DepthSort () int depth = 1; SpriteRenderer sr; Vector2 pos = new Vector2 (); voor (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; // nieuwe depth depth ++; // increment depth

Het mooie van een op 2D-array gebaseerde implementatie is dat voor de juiste isometrische dieetsortering, we alleen een opeenvolgende hogere diepte moeten toewijzen terwijl we het niveau in volgorde doorlopen, met behulp van sequentiële for-lussen. Dit werkt voor ons eenvoudige niveau met slechts een enkele laag grond. Als we meerdere grondniveaus zouden hebben op verschillende hoogten, zou de dieptesortering gecompliceerd kunnen worden.

Al het andere blijft hetzelfde als de 2D-implementatie uitgelegd in de vorige zelfstudie.

Voltooid niveau

Je kunt dezelfde toetsenbordknoppen gebruiken om het spel te spelen. Het enige verschil is dat de held niet verticaal of horizontaal maar isometrisch beweegt. Het voltooide niveau lijkt op de onderstaande afbeelding.

Bekijk hoe de dieptesortering duidelijk zichtbaar is met onze nieuwe blockTiles.

Dat was niet moeilijk, toch? Ik nodig u uit om de niveau-gegevens in het tekstbestand te wijzigen om nieuwe niveaus uit te proberen. De volgende is de zeshoekige versie, die een beetje ingewikkelder is, en ik zou u adviseren om even te pauzeren om met de isometrische versie te spelen voordat u verder gaat.

2. Hexagonal Sokoban Game

De zeshoekige versie van het Sokoban-niveau lijkt op de onderstaande afbeelding.

Zeshoekige weergave

We gebruiken de horizontale uitlijning voor het hexagonale raster voor deze zelfstudie. De theorie achter de zeshoekige implementatie vereist veel meer lezen. Raadpleeg deze tutorialserie voor een basisbegrip. De theorie is geïmplementeerd in de helperklasse HexHelperHorizontal, welke te vinden is in de utils map.

Zeshoekige coördinatenconversie

De HexagonalSokoban Spelscript maakt gebruik van gemaksmethoden uit de helperklasse voor coördinatenomzettingen en andere hexagonale functies. De helperklasse HexHelperHorizontal werkt alleen met een horizontaal uitgelijnd zeshoekig raster. Het bevat methoden voor het converteren van coördinaten tussen offset, axiale en kubieke systemen.

De offset-coördinaat is dezelfde 2D Cartesiaanse coördinaat. Het bevat ook een getNeighbors methode, die een axiale coördinaat inneemt en een a retourneert Lijst met alle zes buren van die celcoördinaat. De volgorde van de lijst is met de klok mee, te beginnen met de celcoördinaat van de noordoostelijke buurman.

Veranderingen in besturingselementen

Met een zeshoekig raster hebben we zes bewegingsrichtingen in plaats van vier, omdat de zeshoek zes zijden heeft terwijl een vierkant er vier heeft. We hebben dus zes toetsenbordtoetsen om de bewegingen van onze held te besturen, zoals te zien is in de onderstaande afbeelding.

De toetsen zijn gerangschikt in dezelfde lay-out als een zeshoekig raster als u de toetsenbordtoets beschouwt S als de middelste cel, met alle controle toetsen als zijn zeshoekige buren. Het helpt de verwarring te verminderen door de beweging te regelen. De bijbehorende wijzigingen in de invoercode zijn zoals hieronder.

private void ApplyUserInput () // we hebben 6 bewegingsrichtingen die worden bestuurd door e, d, x, z, a, w in een cyclische reeks beginnend met NE tot NW als (Input.GetKeyUp (userInputKeys [0])) TryMoveHero (0); // noordoost else if (Input.GetKeyUp (userInputKeys [1])) TryMoveHero (1); // east else if (Input.GetKeyUp (userInputKeys [2])) TryMoveHero (2) ; // zuidoost else if (Input.GetKeyUp (userInputKeys [3])) TryMoveHero (3); // south west else if (Input.GetKeyUp (userInputKeys [4])) TryMoveHero (4); / / west else if (Input.GetKeyUp (userInputKeys [5])) TryMoveHero (5); // noordwest

Er is geen verandering in de kunst en er zijn geen spilwijzigingen nodig.

Andere wijzigingen in de code

Ik zal de codeveranderingen uitleggen met betrekking tot de originele 2D Sokoban-zelfstudie en niet de isometrische versie hierboven. Raadpleeg de gekoppelde broncode voor deze zelfstudie. Het meest interessante feit is dat bijna alle code hetzelfde blijft. De CreateLevel methode heeft slechts één wijziging, namelijk de middleOffset berekening.

middleOffset.x = cols * tileWidth + tileWidth * 0.5f; // dit is gewijzigd voor hexagonal middleOffset.y = rows * tileSize * 3/4 ​​+ tileSize * 0.75f; // dit is gewijzigd voor isometrisch 

Een belangrijke verandering is natuurlijk de manier waarop de schermcoördinaten worden gevonden in de GetScreenPointFromLevelIndices methode.

Vector2 GetScreenPointFromLevelIndices (int row, int col) // indices omzetten in positiewaarden, col bepaalt x en rij bepaalt y Vector2 tempPt = new Vector2 (row, col); tempPt = HexHelperHorizontal.offsetToAxial (tempPt); // converteer van offset naar axiaal // converteer axiaal punt naar schermpunt tempPt = HexHelperHorizontal.axialToScreen (tempPt, sideLength); tempPt.x- = middleOffset.x-Screen.width / 2; // voeg offsets toe voor middle align tempPt.y * = - 1; // eenheids y-ascorrectie tempPt.y + = middleOffset.y-Screen.height / 2; terugkeer tempPt; 

Hier gebruiken we de helperklasse om de coördinaat eerst in axiaal om te zetten en vervolgens de bijbehorende schermcoördinaat te vinden. Let op het gebruik van de zijlengte variabele voor de tweede conversie. Het is de waarde van de lengte van een zijde van de zeshoekige tegel, die opnieuw gelijk is aan de helft van de afstand tussen de twee puntige uiteinden van de zeshoek. Vandaar:

sidelength = tileSize * 0.5f;

De enige andere verandering is de GetNextPositionAlong methode, die wordt gebruikt door de TryMoveHero methode om de volgende cel in een bepaalde richting te vinden. Deze methode is volledig aangepast aan de geheel nieuwe lay-out van ons raster.

private Vector2 GetNextPositionAlong (Vector2 objPos, int-richting) // deze methode is volledig gewijzigd om rekening te houden met de verschillende manier waarop buren worden gevonden in hexagonale logica objPos = HexHelperHorizontal.offsetToAxial (objPos); // converteren van offset naar axiale lijst buren = HexHelperHorizontal.getNeighbors (objPos); objPos = buren [richting]; // de naburige lijst volgt dezelfde volgorde volgorde objPos = HexHelperHorizontal.axialToOffset (objPos); // converteer terug van axiale naar offset-return objPos; 

Met behulp van de helperklasse kunnen we eenvoudig de coördinaten van de buur in de gegeven richting retourneren.

Al het andere blijft hetzelfde als de oorspronkelijke 2D-implementatie. Dat was niet moeilijk, toch? Dat gezegd hebbende, het is niet eenvoudig om te begrijpen hoe we tot de conversievergelijkingen zijn gekomen door de hexagonale tutorial te volgen, wat de kern van het hele proces is. Als je het niveau speelt en voltooit, krijg je het resultaat zoals hieronder.

Conclusie

Het belangrijkste element in beide conversies was de conversie van coördinaten. De isometrische versie omvat aanvullende veranderingen in de techniek met hun draaipunt evenals de noodzaak voor dieptesortering.

Ik geloof dat je hebt gemerkt hoe eenvoudig het is om op rasters gebaseerde spellen te maken met behulp van slechts tweedimensionale array-gebaseerde niveaugegevens en een op tegels gebaseerde benadering. Er zijn onbeperkte mogelijkheden en games die je kunt creëren met dit nieuwe begrip.

Als u alle concepten die we tot nu toe hebben besproken, hebt begrepen, zou ik u willen uitnodigen om de controlemethode te wijzigen om tikken aan te vatten en wat padvinden toe te voegen. Succes.