Creazione di mondi isometrici un primer per gli sviluppatori di giochi

In questo tutorial, ti darò un'ampia panoramica di ciò che devi sapere per creare mondi isometrici. Imparerai cos'è la proiezione isometrica e come rappresentare i livelli isometrici come array 2D. Formuleremo le relazioni tra la vista e la logica, in modo da poter facilmente manipolare gli oggetti sullo schermo e gestire il rilevamento delle collisioni basato su tile. Vedremo anche l'ordinamento in profondità e l'animazione dei personaggi.

Per velocizzare lo sviluppo del gioco, puoi trovare una gamma di risorse di gioco isometriche su Envato Market, pronte per l'uso nel tuo gioco.

Risorse di gioco isometriche sul mercato Envato Post correlati

Vuoi ancora più suggerimenti sulla creazione di mondi isometrici? Controlla il post successivo, Creazione di mondi isometrici: A Primer for Gamedevs, Continua, e il libro di Juwal, Starling Game Development Essentials.


1. Il mondo isometrico

Vista isometrica è un metodo di visualizzazione utilizzato per creare un'illusione di 3D per un gioco altrimenti 2D, a volte indicato come pseudo 3D o 2.5D. Queste immagini (tratte da Diablo 2 e Age of Empires) illustrano cosa intendo:

Diablo 2 L'età degli imperi

L'implementazione di una vista isometrica può essere eseguita in molti modi, ma per motivi di semplicità mi concentrerò su a basata tile- approccio, che è il metodo più efficiente e ampiamente utilizzato. Ho sovrapposto ogni schermata sopra con una griglia di diamanti che mostra come il terreno è diviso in tessere.


2. Giochi basati su tessere

Nell'approccio basato su tessere, ogni elemento visivo è suddiviso in parti più piccole, chiamate tessere, di dimensioni standard. Queste tessere saranno disposte in modo da formare il mondo del gioco in base a dati di livello predeterminati, di solito un array 2D.

Post correlati
  • Tutorial su piastrelle di Tony Pa.

Ad esempio, consideriamo una vista 2D top-down standard con due tessere: una tessera erba e una tessera muro, come mostrato qui:

Alcune tessere semplici

Queste tessere hanno le stesse dimensioni l'una dell'altra e sono quadrate, quindi l'altezza e la larghezza delle piastrelle sono uguali.

Per un livello con i prati racchiusi su tutti i lati dalle pareti, l'array 2D dei dati di livello sarà simile a questo:

[[1,1,1,1,1,1], [1,0,0,0,0,1,1], [1,0,0,0,0,1], [1,0,0, 0,0,1], [1,0,0,0,0,1], [1,1,1,1,1,1]]

Qui, 0 denota una tessera erba e 1 denota un muro di piastrelle. Disporre le tessere in base ai dati di livello produrrà l'immagine di livello inferiore:

Un livello semplice, visualizzato in una vista dall'alto.

Possiamo potenziarlo aggiungendo tessere angolari e separando le tessere verticali e orizzontali, richiedendo cinque tessere aggiuntive:

[[3,1,1,1,1,4], [2,0,0,0,0,2,2], [2,0,0,0,0,2], [2,0,0, 0,0,2], [2,0,0,0,0,2], [6,1,1,1,1,5]]
Livello avanzato con numeri di tessere

Spero che il concetto dell'approccio basato su tile sia ora chiaro. Questa è una semplice implementazione della griglia 2D, che potremmo codificare in questo modo:

for (i, loop through rows) per (j, loop through columns) x = j * tile width y = i * tile tile tileType = levelData [i] [j] placetile (tileType, x, y)

Qui assumiamo che la larghezza della piastrella e l'altezza della piastrella siano uguali (e uguali per tutte le tessere) e corrispondano alle dimensioni delle immagini delle tessere. Pertanto, la larghezza della piastrella e l'altezza della piastrella per questo esempio sono entrambi di 50 px, che costituiscono la dimensione di livello totale di 300x300px, ovvero sei righe e sei colonne di piastrelle di dimensioni 50x50 px ciascuna.

In un normale approccio basato su tile, implementiamo una vista dall'alto o una vista laterale; per una vista isometrica dobbiamo implementare il proiezione isometrica.


3. Proiezione isometrica

La migliore spiegazione tecnica di cosa significhi "proiezione isometrica", per quanto ne so, è tratta da questo articolo di Clint Bellanger:

Angoliamo la videocamera su due assi (spostiamo la fotocamera di 45 gradi su un lato, quindi di 30 gradi verso il basso). Ciò crea una griglia a forma di rombo (rombo) in cui gli spazi della griglia sono due volte più larghi dell'altezza. Questo stile è stato reso popolare dai giochi di strategia e dai giochi di ruolo d'azione. Se guardiamo un cubo in questa vista, sono visibili tre lati (lato superiore e due lati opposti).

Anche se sembra un po 'complicato, in realtà l'implementazione di questa vista è semplice. Quello che dobbiamo capire è la relazione tra lo spazio 2D e lo spazio isometrico - cioè la relazione tra il livello dei dati e la vista; la trasformazione dalle coordinate "cartesiane" dall'alto verso il basso alle coordinate isometriche.

Griglia cartesiana contro griglia isometrica.

(Non stiamo considerando una tecnica basata su tessere esagonali, che è un altro modo di implementare mondi isometrici).

Posizionamento di piastrelle isometriche

Vorrei provare a semplificare la relazione tra i dati di livello memorizzati come array 2D e la vista isometrica, ovvero come trasformiamo le coordinate cartesiane in coordinate isometriche.

Cercheremo di creare la vista assonometrica per i nostri dati a livello di erba del muro:

[[1,1,1,1,1,1], [1,0,0,0,0,1,1], [1,0,0,0,0,1], [1,0,0, 0,0,1], [1,0,0,0,0,1], [1,1,1,1,1,1]]

In questo scenario possiamo determinare un'area percorribile controllando se l'elemento dell'array è 0 a quella coordinata, indicando in tal modo l'erba. L'implementazione della vista 2D del livello di cui sopra era una semplice iterazione con due anelli, posizionando le tessere quadrate che sfalsano ciascuna con l'altezza della piastrella fissa e i valori della larghezza della piastrella.

for (i, loop through rows) per (j, loop through columns) x = j * tile width y = i * tile tile tileType = levelData [i] [j] placetile (tileType, x, y)

Per la vista isometrica, il codice rimane lo stesso, ma il placeTile () la funzione cambia.

Per una vista isometrica dobbiamo calcolare le coordinate isometriche corrispondenti all'interno dei loop.
Le equazioni per fare questo sono le seguenti, dove isoX e isoY rappresentare le coordinate x e isometriche e cartX e Carty rappresentare le coordinate X e Y cartesiane:

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

Queste funzioni mostrano come è possibile convertire da un sistema all'altro:

function isoTo2D (pt: Point): Point var tempPt: Point = new Point (0, 0); tempPt.x = (2 * pt.y + pt.x) / 2; tempPt.y = (2 * pt.y - pt.x) / 2; ritorno (tempPt); 
function twoDToIso (pt: Point): Point var tempPt: Point = new Point (0,0); tempPt.x = pt.x - pt.y; tempPt.y = (pt.x + pt.y) / 2; ritorno (tempPt); 

Lo pseudocodice per il ciclo appare in questo modo:

for (i, loop through rows) per (j, loop through columns) x = j * tile width y = i * tile tile tileType = levelData [i] [j] placetile (tileType, twoDToIso (new Point (x, y) ))
La nostra prateria muraria in una vista isometrica.

Ad esempio, vediamo come una tipica posizione 2D viene convertita in una posizione isometrica:

Punto 2D = [100, 100]; // twoDToIso (punto 2D) verrà calcolato come sotto isoX = 100 - 100; // = 0 isoY = (100 + 100) / 2; // = 100 Iso point == [0, 100];

Allo stesso modo, un input di [0, 0] risulterà in [0, 0], e [10, 5] darà [5, 7.5].

Il metodo sopra ci consente di creare una correlazione diretta tra i dati di livello 2D e le coordinate isometriche. Possiamo trovare le coordinate della piastrella nei dati di livello dalle sue coordinate cartesiane usando questa funzione:

function getTileCoordinates (pt: Point, tileHeight: Number): Point var tempPt: Point = new Point (0, 0); tempPt.x = Math.floor (pt.x / tileHeight); tempPt.y = Math.floor (pt.y / tileHeight); ritorno (tempPt); 

(Qui, assumiamo essenzialmente che l'altezza della piastrella e la larghezza delle piastrelle siano uguali, come nella maggior parte dei casi).

Quindi, da una coppia di coordinate (isometriche) dello schermo, possiamo trovare le coordinate della piastrella chiamando:

getTileCoordinates (isoTo2D (screen point), altezza del riquadro);

Questo punto dello schermo potrebbe essere, ad esempio, una posizione di clic del mouse o una posizione di ripresa.

Mancia: Un altro metodo di posizionamento è il modello Zigzag, che ha un approccio completamente diverso.

Spostamento in coordinate isometriche

Il movimento è molto semplice: puoi manipolare i dati del tuo mondo di gioco in coordinate cartesiane e usare semplicemente le funzioni di cui sopra per aggiornarlo sullo schermo. Ad esempio, se vuoi spostare un carattere in avanti nella direzione positiva y, puoi semplicemente incrementarlo y proprietà e quindi convertire la sua posizione in coordinate isometriche:

y = y + velocità; placetile (twoDToIso (new Point (x, y)))

Ordinamento della profondità

Oltre al normale posizionamento, dovremo occuparcene ordinamento in profondità per disegnare il mondo isometrico. Questo assicura che gli oggetti più vicini al giocatore siano disegnati sopra gli oggetti più lontani.

Il metodo di ordinamento della profondità più semplice consiste semplicemente nell'utilizzare il valore di coordinata y cartesiana, come menzionato in questo suggerimento rapido: più lo schermo è sopra l'oggetto, prima dovrebbe essere disegnato. Funziona bene fino a quando non abbiamo sprite che occupino più di un singolo spazio di tessere.

Il metodo più efficace per l'ordinamento in profondità dei mondi isometrici consiste nel suddividere tutte le tessere in dimensioni standard a piastrella singola e non consentire immagini più grandi. Ad esempio, qui c'è una tessera che non si adatta alle dimensioni standard delle tessere - guarda come possiamo dividerle in più riquadri che corrispondono alle dimensioni della piastrella:

Un'immagine grande è suddivisa in più riquadri di dimensioni isometriche standard

4. Creare l'arte

L'arte isometrica può essere pixel art, ma non deve essere. Quando si tratta di pixel art isometrici, la guida di RhysD ti dice quasi tutto ciò che devi sapere. Alcune teorie possono essere trovate anche su Wikipedia.

Quando si crea l'arte isometrica, le regole generali sono

  • Inizia con una griglia isometrica vuota e aderisci alla perfetta precisione dei pixel.
  • Prova a rompere l'arte in singole immagini di piastrelle isometriche.
  • Cerca di assicurarti che ogni tessera sia calpestabile o non percorribile. Sarà complicato se dovessimo ospitare una singola tessera che contenga aree percorribili e non percorribili.
  • La maggior parte delle tessere dovrà essere tessuta senza problemi in una o più direzioni.
  • Le ombre possono essere difficili da implementare, a meno che non usiamo un approccio a strati in cui disegniamo ombre sul livello di base e quindi disegniamo l'eroe (o alberi o altri oggetti) sul livello superiore. Se l'approccio che usi non è multistrato, assicurati che le ombre cadano in avanti in modo che non cadano, per esempio, sull'eroe quando si trova dietro un albero.
  • Nel caso in cui sia necessario utilizzare un'immagine della tessera più grande della dimensione standard della piastrella isometrica, provare a utilizzare una dimensione che è un multiplo della dimensione della piastrella iso. È meglio avere un approccio a strati in questi casi, in cui possiamo dividere l'arte in diversi pezzi in base alla sua altezza. Ad esempio, un albero può essere diviso in tre pezzi: la radice, il tronco e il fogliame. In questo modo è più facile ordinare le profondità in quanto possiamo disegnare pezzi in strati corrispondenti che corrispondono alle loro altezze.

Le tessere isometriche che sono più grandi delle dimensioni delle singole tessere creeranno problemi con l'ordinamento in profondità. Alcuni dei problemi sono discussi in questi collegamenti:

Post correlati
  • Piastrelle più grandi.
  • Algoritmo di Splitting e Painter.
  • Il post di Openspace sui modi efficaci per dividere le tessere più grandi.

5. Caratteri isometrici

L'implementazione dei caratteri nella vista isometrica non è complicata come potrebbe sembrare. L'arte del personaggio deve essere creata secondo determinati standard. Per prima cosa dovremo correggere quante direzioni di movimento sono consentite nel nostro gioco - in genere i giochi forniranno un movimento a quattro vie o un movimento a otto direzioni.

Direzioni di navigazione a otto direzioni nelle viste dall'alto e in basso.

Per una vista dall'alto, potremmo creare una serie di animazioni di personaggi rivolte in una direzione e ruotarle semplicemente per tutte le altre. Per l'arte dei caratteri isometrici, dobbiamo ricreare ogni animazione in ciascuna delle direzioni consentite, quindi per il movimento a otto direzioni dobbiamo creare otto animazioni per ogni azione. Per facilità di comprensione di solito denotiamo le direzioni come Nord, Nord-Ovest, Ovest, Sud-Ovest, Sud, Sud-Est, Est e Nord-Est, in senso antiorario, in questo ordine.

Un personaggio isometrico rivolto in direzioni diverse.

Posizioniamo i personaggi nello stesso modo in cui posizioniamo le tessere. Il movimento di un personaggio si ottiene calcolando il movimento in coordinate cartesiane e quindi convertendo in coordinate isometriche. Supponiamo che stiamo usando la tastiera per controllare il personaggio.

Imposteremo due variabili, dX e dY, in base ai tasti direzionali premuti. Di default queste variabili saranno 0, e sarà aggiornato come da tabella qui sotto, dove U, D, R e L denota il Su, Giù, Destra e Sinistra tasti freccia, rispettivamente. Un valore di 1 sotto una chiave rappresenta quel tasto premuto; 0 implica che il tasto non venga premuto.

 Chiave Pos UDRL dX dY ================ 0 0 0 0 0 0 1 0 0 0 0 1 0 1 0 0 0 -1 0 0 1 0 1 0 0 0 0 1 -1 0 1 0 1 0 1 1 1 0 0 1 -1 1 0 1 1 0 1 -1 0 1 0 1 -1 -1

Ora, usando i valori di dX e dY, possiamo aggiornare le coordinate cartesiane in questo modo:

newX = currentX + (dX * velocità); newY = currentY + (dY * speed);

Così dX e dY indica il cambiamento delle posizioni x e y del carattere, in base ai tasti premuti.

Possiamo facilmente calcolare le nuove coordinate isometriche, come abbiamo già discusso:

Iso = twoDToIso (new Point (newX, newY))

Una volta ottenuta la nuova posizione isometrica, è necessario mossa il personaggio in questa posizione. Basato sui valori che abbiamo per dX e dY, possiamo decidere in quale direzione si trova il personaggio e usare l'arte del personaggio corrispondente.

Rilevamento collisione

Il rilevamento delle collisioni viene eseguito controllando se la tessera nella nuova posizione calcolata è una tessera non percorribile. Quindi, una volta trovata la nuova posizione, non spostiamo immediatamente il personaggio lì, ma prima controlliamo per vedere quale tessera occupa quello spazio.

tile coordinata = getTileCoordinates (isoTo2D (iso point), tile height); if (isWalkable (tile tile)) moveCharacter ();  else // non fare nulla; 

Nella funzione isWalkable (), controlliamo se il valore dell'array di dati di livello alla coordinata data è una tessera calpestabile o meno. Dobbiamo fare attenzione ad aggiornare la direzione in cui il personaggio si trova ad affrontare - anche se non si muove, come nel caso di lui che colpisce una tessera non percorribile.

Ordinamento della profondità con caratteri

Considera un personaggio e una tessera albero nel mondo isometrico.

Per comprendere correttamente l'ordinamento in profondità, dobbiamo capire che ogni volta che le coordinate xey del personaggio sono inferiori a quelle dell'albero, l'albero si sovrappone al personaggio. Ogni volta che le coordinate xey del personaggio sono maggiori di quelle dell'albero, il personaggio si sovrappone all'albero.

Quando hanno la stessa coordinata x, decidiamo in base solo alla coordinata y: quale che abbia la coordinata y più alta si sovrappone all'altra. Quando hanno la stessa coordinata y, decidiamo in base alla sola coordinata x: a seconda di quale sia la coordinata x più alta si sovrappone all'altra.

Una versione semplificata di questo è solo disegnare in sequenza i livelli partendo dalla tessera più lontana - cioè, tile [0] [0] - quindi disegna tutte le tessere di ciascuna fila una alla volta. Se un personaggio occupa una tessera, prima disegniamo la tessera terreno e poi la tessera personaggio. Questo funzionerà bene, perché il personaggio non può occupare una tessera muro.

L'ordinamento della profondità deve essere eseguito ogni volta che una piastrella cambia posizione. Ad esempio, dobbiamo farlo quando i personaggi si muovono. Quindi aggiorniamo la scena visualizzata, dopo aver eseguito l'ordinamento della profondità, per riflettere i cambiamenti di profondità.


6. Have a Go!

Ora, metti a frutto le tue nuove conoscenze creando un prototipo funzionante, con controlli da tastiera e corretta selezione della profondità e rilevamento delle collisioni. Ecco la mia demo:

Fare clic per assegnare lo stato attivo SWF, quindi utilizzare i tasti freccia. Clicca qui per la versione completa.

Puoi trovare utile questa classe di utilità (l'ho scritta in AS3, ma dovresti essere in grado di capirlo in qualsiasi altro linguaggio di programmazione):

pacchetto com.csharks.juwalbose import flash.display.Sprite; import flash.geom.Point; IsoHelper di classe pubblica / ** * converte un punto isometrico in 2D * * / funzione statica pubblica isoTo2D (pt: Point): Point // gx = (2 * isoy + isox) / 2; // gy = (2 * isoy-isox) / 2 var tempPt: Point = new Point (0,0); tempPt.x = (2 * pt.y + pt.x) / 2; tempPt.y = (2 * pt.y-pt.x) / 2; ritorno (tempPt);  / ** * converti un punto 2d in isometrico * * / public static function twoDToIso (pt: Point): Point // gx = (isox-isoxy; // gy = (isoy + isox) / 2 var tempPt: Point = new Point (0,0); tempPt.x = pt.x-pt.y; tempPt.y = (pt.x + pt.y) / 2; return (tempPt); / ** * converti in 2d punto per riga / colonna di tile specifiche * * / funzione statica pubblica getTileCoordinates (pt: Point, tileHeight: Number): Point var tempPt: Point = new Point (0,0); tempPt.x = Math.floor (pt.x / tileHeight); tempPt.y = Math.floor (pt.y / tileHeight); return (tempPt); / ** * conversione di tile / riga di tile specifiche a 2d point * * / public static function get2dFromTileCoordinates (pt: Point, tileHeight: Number): Point var tempPt: Point = new Point (0,0); tempPt.x = pt.x * tileHeight; tempPt.y = pt.y * tileHeight; return (tempPt);

Se rimani davvero bloccato, ecco il codice completo della mia demo (in formato di codice timeline Flash e AS3):

// Utilizza la classe KeyObject di senocular // http://www.senocular.com/flash/actionscript/?file=ActionScript_3.0/com/senocular/utils/KeyObject.as import flash.display.Sprite; import com.csharks.juwalbose.IsoHelper; import flash.display.MovieClip; import flash.geom.Point; import flash.filters.GlowFilter; import flash.events.Event; import com.senocular.utils.KeyObject; import flash.ui.Keyboard; import flash.display.Bitmap; import flash.display.BitmapData; import flash.geom.Matrix; import flash.geom.Rectangle; var levelData = [[1,1,1,1,1,1], [1,0,0,2,0,1], [1,0,1,0,0,1], [1,0 , 0,0,0,1], [1,0,0,0,0,1,1, [1,1,1,1,1,1]]; var tileWidth: uint = 50; var borderOffsetY: uint = 70; var borderOffsetX: uint = 275; var facing: String = "south"; var currentFacing: String = "south"; var hero: MovieClip = new herotile (); hero.clip.gotoAndStop (fronte); var heroPointer: Sprite; chiave var: KeyObject = new KeyObject (stage); // Senocular KeyObject Class var heroHalfSize: uint = 20; // the tiles var grassTile: MovieClip = new TileMc (); grassTile.gotoAndStop (1); var wallTile: MovieClip = new TileMc (); wallTile.gotoAndStop (2); // il canvas var bg: Bitmap = new Bitmap (new BitmapData (650,450)); addChild (bg); var rect: Rectangle = bg.bitmapData.rect; // per gestire depth var overlayContainer: Sprite = new Sprite (); addChild (overlayContainer); // per gestire il movimento della direzione var dX: Number = 0; var dY: Number = 0; var idle: Boolean = true; var speed: uint = 5; var heroCartPos: Point = new Point (); var heroTile: Point = new Point (); // aggiungi elementi al livello iniziale, aggiungi la funzione del ciclo di gioco createLevel () var tileType: uint; per (var i: uint = 0; i 

Punti di registrazione

Presta particolare attenzione ai punti di registrazione delle tessere e dell'eroe. (I punti di registrazione possono essere considerati come i punti di origine per ciascun particolare sprite.) Questi generalmente non cadranno all'interno dell'immagine, ma piuttosto saranno l'angolo in alto a sinistra del riquadro di delimitazione dello sprite.

Dovremo modificare il nostro codice di disegno per correggere correttamente i punti di registrazione, principalmente per l'eroe.

Rilevamento collisione

Un altro punto interessante da notare è che calcoliamo il rilevamento delle collisioni in base al punto in cui si trova l'eroe.

Ma l'eroe ha un volume, e non può essere rappresentato con precisione da un singolo punto, quindi dobbiamo rappresentare l'eroe come un rettangolo e controllare le collisioni contro ogni angolo di questo rettangolo in modo che non ci siano sovrapposizioni con altre tessere e quindi nessun artefatto di profondità.

Tasti di scelta rapida

Nella demo, ho semplicemente ridisegnato la scena di nuovo ogni fotogramma in base alla nuova posizione dell'eroe. Troviamo la tessera che l'eroe occupa e disegna l'eroe sopra la tessera terreno quando i loop di rendering raggiungono quelle tessere.

Ma se guardiamo più da vicino, scopriremo che non c'è bisogno di scorrere tutte le tessere in questo caso. Le tessere erba e le tessere superiore e sinistra sono sempre disegnate prima che l'eroe venga pescato, quindi non abbiamo mai bisogno di ridisegnarle. Inoltre, le tessere inferiore e destra sono sempre davanti all'eroe e quindi disegnate dopo l'eroe è disegnato.

Essenzialmente, quindi, abbiamo solo bisogno di eseguire un ordinamento di profondità tra il muro all'interno dell'area attiva e l'eroe, cioè due tessere. Notare questi tipi di scorciatoie ti aiuterà a risparmiare un sacco di tempo di elaborazione, che può essere cruciale per le prestazioni.


Conclusione

A questo punto, dovresti avere una buona base per costruire giochi isometrici di tua scelta: puoi rendere il mondo e gli oggetti in esso contenuti, rappresentare i dati di livello in semplici array 2D, convertire tra coordinate cartesiane e isometriche e trattare concetti come l'ordinamento di profondità e animazione dei personaggi. Divertiti a creare mondi isometrici!

Post correlati
  • Creazione di mondi isometrici: un primer per Gamedevs, continua
  • Suggerimento rapido: livelli isometrici economici e facili
  • Matematica di piastrelle isometriche
  • 6 Guide incredibilmente approfondite per lo sviluppo e il design di giochi per principianti