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.
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 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);
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.
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.
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.
De zeshoekige versie van het Sokoban-niveau lijkt op de onderstaande afbeelding.
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.
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.
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.
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 lijstburen = 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.
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.