Unitatea 2D, bazată pe jocul izometric și hexagonal, Sokoban

Ce veți crea

În acest tutorial, vom converti un joc convențional bazat pe țiglă 2D Sokoban în vederi izometrice și hexagonale. Dacă sunteți nou în jocuri izometrice sau hexagonale, poate fi copleșitor la început pentru a încerca să le urmăriți pe amândouă în același timp. În acest caz, recomand să alegeți mai întâi izometric și apoi să reveniți într-o etapă ulterioară pentru versiunea hexagonală.

Vom construi pe partea de sus a tutorialului Unity anterior: Sokoban Game Unity 2D Tile-Based. Vă rugăm să treceți prin primul tutorial, deoarece majoritatea codului rămâne neschimbată și toate conceptele de bază rămân aceleași. Voi lega și alte tutoriale care explică unele dintre conceptele care stau la baza lor.

Cel mai important aspect în crearea versiunilor izometrice sau hexagonale dintr-o versiune 2D este imaginea poziționării elementelor. Vom folosi metode de conversie bazate pe ecuații pentru a converti între diferitele sisteme de coordonate.

Acest tutorial are două secțiuni, unul pentru versiunea izometrică și altul pentru versiunea hexagonală.

1. Jocul isometric Sokoban

Hai să ne aruncăm înapoi în versiunea izometrică odată ce ați trecut prin tutorialul original. Imaginea de mai jos arată cum va arăta versiunea izometrică, cu condiția să folosim aceleași informații de nivel utilizate în tutorialul original.

Vizualizare izometrică

Teoria izometrică, ecuația de conversie și implementarea sunt explicate în mai multe tutoriale pe Envato Tuts +. O veche explicație bazată pe Flash poate fi găsită în acest tutorial detaliat. Aș recomanda acest tutorial bazat pe Phaser, deoarece este o dovadă mai recentă și viitoare.

Deși limbile de scripting utilizate în aceste tutoriale sunt ActionScript 3 și respectiv JavaScript, teoria este aplicabilă peste tot, indiferent de limbile de programare. În esență se reduce la aceste ecuații de conversie care urmează să fie utilizate pentru a converti coordonatele cartezian 2D la coordonatele izometrice sau invers.

// Cartesian la izometric: isoX = cartX - cartY; izoY = (cartX + cartY) / 2; // izometric la cartesian: cartX = (2 * isoY + isoX) / 2; cartY = (2 x izo-izoX) / 2;

Vom folosi următoarea funcție Unity pentru conversia în coordonate izometrice.

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

Modificările din art

Vom folosi aceleași informații de nivel pentru a crea matricea noastră 2D, levelData, care va conduce reprezentarea izometrică. Majoritatea codului vor rămâne aceleași, altele decât cele specifice vederii izometrice.

Cu toate acestea, arta trebuie să aibă unele modificări în ceea ce privește punctele pivotului. Consultați imaginea de mai jos și explicația care urmează.

IsometricSokoban scriptul de jocuri folosește sprite modificate ca heroSprite, ballSprite, și blockSprite. Imaginea arată noile puncte pivot utilizate pentru aceste sprite. Această modificare dă aspectul pseudo-3D pe care îl urmărim cu viziunea izometrică. BlockSprite este un sprite nou pe care îl adăugăm când găsim un invalidTile.

Mă va ajuta să explic cel mai important aspect al jocurilor izometrice, sortarea în adâncime. Deși sprite este doar un hexagon, îl considerăm un cub 3D în care pivotul este situat la mijlocul feței inferioare a cubului.

Modificări în cod

Descărcați codul partajat prin magazia git conectată înainte de a continua. CreateLevel metoda are câteva modificări care se referă la scara și poziționarea plăcilor și la adăugarea acestora blockTile. Scara tileSprite, care este doar o imagine în formă de diamant reprezentând țigla noastră de bază, trebuie să fie modificată ca mai jos.

tile.transform.localScale = nou Vector2 (tileSize-1, (tileSize-1) / 2); // dimensiunea este critică pentru forma isometrică

Aceasta reflectă faptul că o piesă izometrică va avea o înălțime de jumătate din lățimea sa. heroSprite si ballSprite au o dimensiune de tileSize / 2.

hero.transform.localScale = Vector2.one * (tileSize / 2); / / folosim jumătate din dimensiunea tiles pentru ocupanți

Oriunde găsim un invalidTile, adăugăm a blockTile utilizând următorul cod.

tile = new GameObject ("bloc" + i.ToString () + "_" + j.ToString ()); float newDimension = 2 * tilesSize / rootThree; tile.transform.localScale = nou Vector2 (newDimension, tileSize); // trebuie să setăm o înălțime sr = tile.AddComponent(); // adăugați un renderer sprite sr.sprite = blockSprite; // atribuiți bloc sprite sr.sortingOrder = 1; // de asemenea, trebuie să aveți ordine mai mare de sortare Color c = Color.gray; C.a = 0.9f; sr.color = c; tile.transform.position = GetScreenPointFromLevelIndices (i, j); // locul in scena bazat pe indici de nivel ocupants.Add (tile, new Vector2 (i, j)); // stoca indicii de nivel ai blocului in dict

Hexagonul trebuie să fie scalat diferit pentru a obține aspectul izometric. Aceasta nu va fi o problemă atunci când arta este gestionată de artiști. Aplicăm o valoare alfa puțin mai mică pentru blockSprite astfel încât să putem vedea prin ea, ceea ce ne permite să vedem adâncimea de sortare în mod corespunzător. Observați că adăugăm aceste plăci la ocupanți dicționar, care va fi folosit ulterior pentru sortarea adâncimii.

Poziționarea plăcilor se face folosind GetScreenPointFromLevelIndices , care la rândul său folosește CartesianToIsometric metoda de conversie explicată mai devreme. Y axa puncte în direcția opusă pentru unitate, care trebuie să fie luată în considerare în timp ce adăugați middleOffset pentru a poziționa nivelul în mijlocul ecranului.

Vector2 GetScreenPointFromLevelIndices (int rând, int col) // convertiți indicii la valori de poziție, col determină x și rând determina y Vector2 tempPt = CartesianToIsometric (nou Vector2 (col * tileSize / 2, row * tileSize / 2)); '-' în partea y ca o corecție a axei se poate întâmpla după acoperirea tempPt.x- = middleOffset.x; / / aplicăm offsetul în afara conversiei coordonatelor pentru alinierea nivelului în mijlocul ecranului tempPt.y * = - 1; // unitatea y corecția axei tempPt.y + = middleOffset.y; // aplicăm decalajul în afara convertirii coordonatelor pentru a alinia nivelul în returul intermediar tempPt; 

La sfârșitul CreateLevel precum și la sfârșitul perioadei TryMoveHero metoda, numim DepthSort metodă. Sortarea în adâncime este cel mai important aspect al unei implementări izometrice. În esență, determinăm ce dale merge în spatele sau în fața altor plăci din nivel. DepthSort metoda este așa cum se arată mai jos.

void privat DepthSort () int adâncime = 1; SpriteRenderer sr; Vector2 pos = noua Vector2 (); pentru (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 = adâncime; // asociere adâncime adâncime ++; // adâncime incrementare

Frumusețea unei implementări bazate pe matrice 2D este aceea că, pentru o sortare corectă a adâncimilor izometrice, trebuie să alocăm adâncime secvențial mai mare în timp ce analizăm nivelul în ordine, folosind secvențiale pentru bucle. Acest lucru funcționează pentru nivelul nostru simplu, cu un singur strat de pământ. Dacă am avea nivele multiple de la diferite înălțimi, atunci sortarea adâncimii ar putea deveni complicată.

Tot ceea ce rămâne rămâne același cu implementarea 2D explicată în tutorialul anterior.

Nivel finalizat

Puteți utiliza aceleași comenzi de la tastatură pentru a reda jocul. Singura diferență este că eroul nu se va mișca vertical sau orizontal, ci izometric. Nivelul terminat ar arăta ca imaginea de mai jos.

Verificați modul în care sortarea de adâncime este vizibilă cu noile noastre blockTiles.

Nu a fost greu, nu? Vă invit să modificați datele de nivel în fișierul text pentru a încerca noi niveluri. Înainte este versiunea hexagonală, care este un pic mai complicată și v-aș sfătui să faceți o pauză pentru a juca cu versiunea izometrică înainte de a continua.

2. Jocul Sokoban hexagonal

Versiunea hexagonală a nivelului Sokoban ar arăta ca imaginea de mai jos.

Vedere hexagonală

Folosim alinierea orizontală pentru grila hexagonală pentru acest tutorial. Teoria din spatele implementării hexagonale necesită o lectură suplimentară. Consultați această serie de tutori pentru o înțelegere de bază. Teoria este implementată în clasa de ajutor HexHelperHorizontal, care pot fi găsite în utils pliant.

Convertirea hexagonală a coordonatelor

HexagonalSokoban scriptul de joc folosește metode convenționale din clasa de ajutor pentru conversii de coordonate și alte caracteristici hexagonale. Clasa de ajutor HexHelperHorizontal va funcționa numai cu o grilă hexagonală aliniată orizontal. Acesta include metode de conversie a coordonatelor între sisteme offset, axiale și cubice.

Coordonarea offset este aceeași coordonată carteziană 2D. De asemenea, include a getNeighbors , care ia o coordonată axială și returnează o Listă cu toți cei șase vecini ai acelei celule. Ordinea listei este în sensul acelor de ceasornic, începând cu coordonatele celulei vecinului nord-est.

Modificări ale comenzilor

Cu o grilă hexagonală, avem șase direcții de mișcare în loc de patru, deoarece hexagonul are șase laturi, în timp ce un pătrat are patru. Deci, avem șase chei de tastatură pentru a controla mișcarea eroului nostru, așa cum se arată în imaginea de mai jos.

Cheile sunt aranjate în același aspect ca o grilă hexagonală, dacă luați în considerare cheia de tastatură S ca celula de mijloc, cu toate cheile de control ca vecinii sai hexagonali. Ajută la reducerea confuziei cu controlul mișcării. Modificările corespunzătoare codului de intrare sunt cele de mai jos.

privat void ApplyUserInput () // avem 6 direcții de mișcare controlate de e, d, x, z, a, w într-o secvență ciclică care începe cu NE la NW dacă (Input.GetKeyUp (userInputKeys [0])) TryMoveHero (Input.GetKeyUp (userInputKeys [1])) TryMoveHero (1); // east altul dacă (Input.GetKeyUp (userInputKeys [2])) TryMoveHero (2) ; // south east altfel dacă (Input.GetKeyUp (userInputKeys [3])) TryMoveHero (3); // south west altceva dacă (Input.GetKeyUp (userInputKeys [4])) TryMoveHero (4) / west altfel dacă (Input.GetKeyUp (userInputKeys [5])) TryMoveHero (5); // north west

Nu există nicio schimbare în artă și nu sunt necesare schimbări pivot.

Alte modificări ale codului

Voi explica modificările de cod în ceea ce privește tutorialul 2D Sokoban original și nu versiunea izometrică de mai sus. Vă rugăm să vă referiți la codul sursă legat de acest tutorial. Cel mai interesant este faptul că aproape tot codul rămâne același. CreateLevel metoda are doar o singură modificare, care este middleOffset calcul.

middleOffset.x = cols * tileWidth + tileWidth * 0.5f; // acesta este schimbat pentru hexagonal middleOffset.y = rows * tileSize * 3/4 ​​+ tileSize * 0.75f; // acesta este schimbat pentru izometric 

O schimbare majoră este în mod evident modul în care coordonatele ecranului se găsesc în GetScreenPointFromLevelIndices metodă.

Vector2 GetScreenPointFromLevelIndices (int rând, int col) // convertiți indicii în valori de poziție, col determină x și rând determina y Vector2 tempPt = new Vector2 (row, col); tempPt = HexHelperHorizontal.offsetToAxial (tempPt); // convertiți de la offset la axial // convertiți punctul axial în punctul de ecran tempPt = HexHelperHorizontal.axialToScreen (tempPt, sideLength); tempPt.x- = middleOffset.x-Screen.width / 2; // adăuga offseturi pentru alinierea middle tempPt.y * = - 1; // unitatea y axa corecție tempPt.y + = middleOffset.y-Screen.height / 2; retur tempPt; 

Aici folosim clasa helper pentru a converti mai întâi coordonatele în axială și apoi găsiți coordonatele de ecran corespunzătoare. Vă rugăm să rețineți utilizarea Lungime laterală variabilă pentru a doua conversie. Este valoarea lungimii unei părți a plăcii hexagonale, care este din nou egală cu jumătate din distanța dintre cele două capete ale hexagonului. De aici:

sideLength = tileSize * 0.5f;

Singura altă modificare este GetNextPositionAlong metoda, care este folosită de TryMoveHero pentru a găsi următoarea celulă într-o anumită direcție. Această metodă este complet modificată pentru a se potrivi cu aspectul complet nou al rețelei noastre.

privat Vector2 GetNextPositionAlong (Vector2 objPos, direcție int) // această metodă este complet modificată pentru a se potrivi felului în care vecinii se găsesc în logica hexagonală objPos = HexHelperHorizontal.offsetToAxial (objPos); // convertiți de la offset la axial List vecinii = HexHelperHorizontal.getNeighbors (objPos); objPos = vecinii [direction]; // lista de vecini urmeaza aceeasi secventa de ordine objPos = HexHelperHorizontal.axialToOffset (objPos); // converti inapoi de la obiecte returnate axial la offset; 

Folosind clasa helper, putem reveni cu ușurință la coordonatele vecinului în direcția dată.

Totul rămâne același cu implementarea inițială 2D. Nu a fost greu, nu? Acestea fiind spuse, înțelegerea modului în care am ajuns la ecuațiile de conversie urmând tutorialul hexagonal, care este esența întregului proces, nu este ușor. Dacă joci și completezi nivelul, vei obține rezultatul ca mai jos.

Concluzie

Elementul principal în ambele conversii a fost conversiile de coordonate. Versiunea izometrică implică schimbări suplimentare în artă cu punctul de pivotare al acestora, precum și nevoia de sortare a adâncimii.

Cred că ați descoperit cât de ușor este să creați jocuri bazate pe grid utilizând doar date de nivel bazate pe matrice și o abordare bazată pe țiglă. Există posibilități nelimitate și jocuri pe care le puteți crea cu această nouă înțelegere.

Dacă ați înțeles toate conceptele pe care le-am discutat până acum, v-aș invita să schimbați metoda de control pentru a selecta și a adăuga unele căi de căutare. Mult noroc.