Gioco isometrico ed esagonale Sokoban basato su tessere Unity 2D

Cosa starai creando

In questo tutorial, convertiremo un gioco Sokoban tradizionale a tessere 2D in viste isometriche ed esagonali. Se sei nuovo ai giochi isometrici o esagonali, all'inizio potresti essere travolgente a provare a seguirli entrambi contemporaneamente. In tal caso, consiglio di scegliere prima l'isometrica e poi di tornare in una fase successiva per la versione esagonale.

Ci occuperemo del precedente tutorial Unity: Unity Sokoban Game basato su tile 2D. Per prima cosa, segui il tutorial poiché la maggior parte del codice rimane invariata e tutti i concetti chiave rimangono gli stessi. Collegherò anche ad altri tutorial che spiegano alcuni dei concetti sottostanti.

L'aspetto più importante nella creazione di versioni isometriche o esagonali da una versione 2D è la determinazione del posizionamento degli elementi. Useremo metodi di conversione basati su equazioni per la conversione tra i vari sistemi di coordinate.

Questo tutorial ha due sezioni, una per la versione isometrica e l'altra per la versione esagonale.

1. Gioco isometrico di Sokoban

Entriamo subito nella versione isometrica dopo aver seguito il tutorial originale. L'immagine sotto mostra come apparirà la versione isometrica, purché utilizziamo le stesse informazioni di livello utilizzate nel tutorial originale.

Vista isometrica

La teoria isometrica, l'equazione di conversione e l'implementazione sono spiegate in più esercitazioni su Envato Tuts +. Una vecchia spiegazione basata su Flash può essere trovata in questo tutorial dettagliato. Consiglierei questo tutorial basato su Phaser poiché è più recente e futuro.

Sebbene i linguaggi di scripting utilizzati in questi tutorial siano rispettivamente ActionScript 3 e JavaScript, la teoria è applicabile ovunque, indipendentemente dai linguaggi di programmazione. In sostanza si riduce a queste equazioni di conversione che devono essere utilizzate per convertire le coordinate cartesiane 2D in coordinate isometriche o viceversa.

// Cartesiano a isometrico: isoX = cartX - cartY; isoY = (cartX + cartY) / 2; // Isometric to Cartesian: cartX = (2 * isoY + isoX) / 2; cartY = (2 * isoY - isoX) / 2;

Utilizzeremo la seguente funzione Unity per la conversione in coordinate isometriche.

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

Modifiche nell'art

Useremo le stesse informazioni di livello per creare il nostro array 2D, levelData, che guiderà la rappresentazione isometrica. Anche la maggior parte del codice rimarrà la stessa, a parte quella specifica per la vista isometrica.

L'arte, tuttavia, deve avere alcune modifiche rispetto ai punti di rotazione. Si prega di fare riferimento all'immagine qui sotto e alla spiegazione che segue.

Il IsometricSokoban lo script di gioco usa sprite modificati come heroSprite, ballSprite, e blockSprite. L'immagine mostra i nuovi punti di rotazione utilizzati per questi sprite. Questo cambiamento dà lo pseudo look 3D a cui miriamo con la vista isometrica. Il blockSprite è un nuovo sprite che aggiungiamo quando troviamo un invalidTile.

Mi aiuterà a spiegare l'aspetto più importante dei giochi isometrici, l'ordinamento in profondità. Sebbene lo sprite sia solo un esagono, lo consideriamo come un cubo 3D in cui il perno si trova al centro della faccia inferiore del cubo.

Modifiche nel codice

Si prega di scaricare il codice condiviso attraverso il repository git collegato prima di procedere ulteriormente. Il CreateLevel il metodo ha alcune modifiche che riguardano la scala e il posizionamento delle tessere e l'aggiunta del blockTile. La scala del tileSprite, che è solo un'immagine a forma di diamante che rappresenta la nostra tessera terreno, deve essere modificata come di seguito.

tile.transform.localScale = new Vector2 (tileSize-1, (tileSize-1) / 2); // size è fondamentale per la forma isometrica

Ciò riflette il fatto che una tessera isometrica avrà un'altezza pari a metà della sua larghezza. Il heroSprite e il ballSprite avere una dimensione di tileSize / 2.

hero.transform.localScale = Vector2.one * (tileSize / 2); // usiamo la metà di tilesize per gli occupanti

Ovunque troviamo un invalidTile, aggiungiamo a blockTile usando il seguente codice.

tile = new GameObject ("block" + i.ToString () + "_" + j.ToString ()); // crea una nuova tile float rootThree = Mathf.Sqrt (3); float newDimension = 2 * tileSize / rootThree; tile.transform.localScale = new Vector2 (newDimension, tileSize); // abbiamo bisogno di impostare l'altezza sr = tile.AddComponent(); // aggiungi un renderizzatore di sprite sr.sprite = blockSprite; // assegna il blocco sprite sr.sortingOrder = 1; // anche questo deve avere un ordine di ordinamento più elevato Colore c = Color.gray; c.a = 0.9f; sr.color = c; tile.transform.position = GetScreenPointFromLevelIndices (i, j); // inserisce nella scena in base agli occupanti degli indici di livello. Aggiungi (tile, new Vector2 (i, j)); // memorizza gli indici di livello del blocco in dict

L'esagono deve essere ridimensionato in modo diverso per ottenere l'aspetto isometrico. Questo non sarà un problema quando l'arte è gestita da artisti. Stiamo applicando un valore alfa leggermente inferiore al blockSprite in modo che possiamo vedere attraverso di esso, che ci permette di vedere correttamente l'ordinamento della profondità. Si noti che stiamo aggiungendo queste tessere al occupanti dizionario, che verrà utilizzato in seguito per l'ordinamento in profondità.

Il posizionamento delle tessere viene fatto usando il GetScreenPointFromLevelIndices metodo, che a sua volta utilizza il CartesianToIsometric metodo di conversione spiegato in precedenza. Il Y punti dell'asse nella direzione opposta per Unity, che deve essere considerato mentre si aggiunge il middleOffset per posizionare il livello al centro dello schermo.

Vector2 GetScreenPointFromLevelIndices (int row, int col) // conversione degli indici in valori di posizione, col determina x & row determinano y Vector2 tempPt = CartesianToIsometric (new Vector2 (col * tileSize / 2, row * tileSize / 2)) // rimosso il '-' nella parte y come correzione dell'asse può avvenire dopo copertone tempPt.x- = middleOffset.x; // applichiamo l'offset al di fuori della conversione delle coordinate per allineare il livello nel mezzo dello schermo tempPt.y * = - 1; // correzione dell'asse y di unità tempPt.y + = middleOffset.y; // applichiamo l'offset all'esterno della conversione di coordinate per allineare il livello in tempPt di ritorno centrale dello schermo; 

Alla fine di CreateLevel metodo così come alla fine del TryMoveHero metodo, chiamiamo il DepthSort metodo. L'ordinamento in profondità è l'aspetto più importante di un'implementazione isometrica. In sostanza, determiniamo quali tessere vanno dietro o davanti alle altre tessere nel livello. Il DepthSort il metodo è come mostrato di seguito.

private void DepthSort () int depth = 1; SpriteRenderer sr; Vector2 pos = new Vector2 (); per (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 = profondità; // assegna una nuova profondità di profondità ++; // incrementa la profondità

La bellezza di un'implementazione basata su array 2D è che per il corretto ordinamento della profondità isometrica, abbiamo solo bisogno di assegnare una profondità sequenzialmente superiore mentre analizziamo il livello in ordine, usando sequenziali per cicli. Questo funziona per il nostro livello semplice con solo un singolo strato di terreno. Se avessimo più livelli di terreno a varie altezze, allora l'ordinamento in profondità potrebbe complicarsi.

Tutto il resto rimane lo stesso dell'implementazione 2D spiegata nel precedente tutorial.

Livello completato

Puoi utilizzare gli stessi comandi da tastiera per giocare. L'unica differenza è che l'eroe non si muoverà verticalmente o orizzontalmente ma in modo isometrico. Il livello finito sarebbe simile all'immagine qui sotto.

Scopri come l'ordinamento della profondità è chiaramente visibile con il nostro nuovo blockTiles.

Non è stato difficile, vero? Vi invito a cambiare i dati di livello nel file di testo per provare nuovi livelli. Il prossimo è la versione esagonale, che è un po 'più complicata, e ti consiglierei di fare una pausa per giocare con la versione isometrica prima di procedere.

2. Gioco esagonale Sokoban

La versione esagonale del livello di Sokoban sarebbe simile all'immagine qui sotto.

Vista esagonale

Stiamo utilizzando l'allineamento orizzontale per la griglia esagonale per questo tutorial. La teoria alla base dell'esecuzione esagonale richiede molte ulteriori letture. Si prega di fare riferimento a questa serie di tutorial per una comprensione di base. La teoria è implementata nella classe helper HexHelperHorizontal, che può essere trovato nel utils cartella.

Conversione di coordinate esagonali

Il HexagonalSokoban Lo script di gioco utilizza i metodi di convenienza della classe helper per le conversioni di coordinate e altre funzionalità esagonali. La classe helper HexHelperHorizontal funzionerà solo con una griglia esagonale allineata orizzontalmente. Include metodi per convertire le coordinate tra sistemi offset, assiali e cubici.

La coordinata dell'offset è la stessa coordinata cartesiana 2D. Include anche a getNeighbors metodo, che accetta una coordinata assiale e restituisce a Elenco con tutti e sei i vicini di quella coordinata cellulare. L'ordine della lista è in senso orario, a partire dalla coordinata della cella del vicino nord-est.

Cambiamenti nei controlli

Con una griglia esagonale, abbiamo sei direzioni di movimento invece di quattro, poiché l'esagono ha sei lati mentre un quadrato ne ha quattro. Quindi abbiamo sei tasti della tastiera per controllare il movimento del nostro eroe, come mostrato nell'immagine qui sotto.

I tasti sono disposti nello stesso layout di una griglia esagonale se si considera il tasto della tastiera S come la cella centrale, con tutti i tasti di controllo come i suoi vicini esagonali. Aiuta a ridurre la confusione con il controllo del movimento. Le modifiche corrispondenti al codice di input sono le seguenti.

private void ApplyUserInput () // abbiamo 6 direzioni di movimento controllate da e, d, x, z, a, w in una sequenza ciclica che inizia con NE a NW se (Input.GetKeyUp (userInputKeys [0])) TryMoveHero (0); // nord est else if (Input.GetKeyUp (userInputKeys [1])) TryMoveHero (1); // east else if (Input.GetKeyUp (userInputKeys [2])) TryMoveHero (2) ; // sud est 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); // north west

Non c'è alcun cambiamento nell'arte e non sono necessari cambiamenti di rotazione.

Altre modifiche nel codice

Spiegherò le modifiche al codice rispetto al tutorial originale Sokoban 2D e non alla versione isometrica sopra. Si prega di fare riferimento al codice sorgente collegato per questo tutorial. Il fatto più interessante è che quasi tutto il codice rimane lo stesso. Il CreateLevel il metodo ha solo un cambiamento, che è il middleOffset calcolo.

middleOffset.x = cols * tileWidth + tileWidth * 0.5f; // questo è cambiato per middleexset esagonale = righe * tileSize * 3/4 ​​+ tileSize * 0.75f; // questo è cambiato per isometrico 

Un importante cambiamento è ovviamente il modo in cui le coordinate dello schermo si trovano nel GetScreenPointFromLevelIndices metodo.

Vector2 GetScreenPointFromLevelIndices (int row, int col) // conversione degli indici in valori di posizione, col determina x e riga determina y Vector2 tempPt = new Vector2 (row, col); tempPt = HexHelperHorizontal.offsetToAxial (tempPt); // conversione da offset a assiale // conversione del punto assiale in punto dello schermo tempPt = HexHelperHorizontal.axialToScreen (tempPt, sideLength); tempPt.x- = middleOffset.x-Screen.width / 2; // aggiungi offset per l'allineamento medio tempPt.y * = - 1; // unità di correzione dell'asse y tempPt.y + = middleOffset.y-Screen.height / 2; return tempPt; 

Qui usiamo la classe helper per convertire prima la coordinata in assiale e quindi trovare la coordinata dello schermo corrispondente. Si prega di notare l'uso del lunghezza laterale variabile per la seconda conversione. È il valore della lunghezza di un lato della tessera esagonale, che è di nuovo pari alla metà della distanza tra le due estremità appuntite dell'esagono. Quindi:

sideLength = tileSize * 0.5f;

L'unico altro cambiamento è il GetNextPositionAlong metodo, che viene utilizzato dal TryMoveHero metodo per trovare la cella successiva in una determinata direzione. Questo metodo è stato completamente modificato per adattarsi al layout completamente nuovo della nostra griglia.

private Vector2 GetNextPositionAlong (Vector2 objPos, int direction) // questo metodo è completamente modificato per adattarsi al diverso modo in cui i vicini si trovano nella logica esagonale objPos = HexHelperHorizontal.offsetToAxial (objPos); // conversione da offset a lista assiale vicini = HexHelperHorizontal.getNeighbors (objPos); objPos = neighbors [direction]; // l'elenco dei vicini segue la stessa sequenza di ordini objPos = HexHelperHorizontal.axialToOffset (objPos); // converti indietro da axial a offset return objPos; 

Usando la classe helper, possiamo facilmente riportare le coordinate del vicino nella direzione indicata.

Tutto il resto rimane lo stesso dell'implementazione 2D originale. Non è stato difficile, vero? Detto questo, capire come siamo arrivati ​​alle equazioni di conversione seguendo il tutorial esagonale, che è il punto cruciale dell'intero processo, non è facile. Se giochi e completi il ​​livello, otterrai il risultato come di seguito.

Conclusione

L'elemento principale in entrambe le conversioni era le conversioni di coordinate. La versione isometrica implica ulteriori cambiamenti nell'arte con il loro punto di articolazione e la necessità di ordinare in profondità.

Credo che tu abbia trovato quanto sia facile creare giochi basati sulla griglia usando solo dati bidimensionali basati su array e un approccio basato su tile. Ci sono possibilità illimitate e giochi che puoi creare con questa nuova comprensione.

Se hai compreso tutti i concetti di cui abbiamo discusso finora, ti invito a cambiare il metodo di controllo per toccare e aggiungere qualche percorso di ricerca. In bocca al lupo.