Un primar actualizat pentru crearea de lumi isometrice, Partea 1

Ce veți crea

Toți ne-am jucat cu toții jocuri isometrice, fie Diablo original, fie Age of Empires or Commandos. Prima dată când ați dat peste un joc izometric, s-ar putea să fi întrebat dacă a fost a Joc 2D sau a Joc 3D sau ceva complet diferit. Lumea jocurilor izometrice are atracția sa mistică și pentru dezvoltatorii de jocuri. Să încercăm să dezvălui misterul proiecției izometrice și să încercăm să creăm o lume simplă izometrică în acest tutorial.

Acest tutorial este o versiune actualizată a tutorialului meu existent privind crearea de lumi isometrice. Tutorialul original folosit Flash cu ActionScript și este încă relevant pentru dezvoltatorii Flash sau OpenFL. În acest tutorial nou am decis să folosesc Phaser cu cod JS, creând astfel ieșire interactivă HTML5 în locul ieșirii SWF. 

Rețineți că acest tutorial nu este un tutorial de dezvoltare Phaser, dar pur și simplu folosim Phaser pentru a comunica cu ușurință conceptele de bază ale creării unei scene izometrice. În plus, există modalități mult mai bune și mai ușoare de a crea conținuturi izometrice în Phaser, cum ar fi pluginul izometric Phaser. 

Din motive de simplitate, vom folosi abordarea bazată pe dale pentru a crea scena noastră izometrică.

1. Jocuri pe bază de țiglă

În jocurile 2D folosind abordarea pe bază de țiglă, fiecare element vizual este împărțit în bucăți mai mici, numite dale, de dimensiuni standard. Aceste plăci vor fi aranjate pentru a forma lumea jocurilor în funcție de datele predefinite - de obicei o matrice bidimensională.

postări asemănatoare

  • Tutorialele bazate pe Tile Pa

De obicei, jocurile pe bază de țiglă utilizează fie a de sus în jos vizualiza sau a vedere laterală pentru scena jocului. Să luăm în considerare o vedere standard 2D de sus în jos cu două plăci - a Placi de iarbă și a plăci de perete-așa cum se arată aici:

Ambele aceste plăci sunt imagini pătrate de aceeași mărime, de unde rezultă tigla înălțime și lățimea plăcii sunt la fel. Să luăm în considerare un nivel de joc care este o pășune închisă pe toate părțile de pereți. Într-un astfel de caz, datele de nivel reprezentate cu o matrice bidimensională vor arăta astfel:

[[1,1,1,1,1,1], [1,0,0,0,0,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]

Aici, 0 desemnează o placă de iarbă și 1 desemnează o placă de perete. Aranjarea plăcilor în funcție de datele de nivel va produce pășunile noastre cu pereți, așa cum se arată în imaginea de mai jos:

Putem merge mai departe adăugând plăci de colț și pereți despărțitori verticali și orizontali, care necesită cinci plăci suplimentare, ceea ce ne duce la datele actualizate la nivel:

[[3,1,1,1,1,4], [2,0,0,0,0,2], [2,0,0,0,0,2], [2,0,0, 0,0,2], [2,0,0,0,0,2], [6,1,1,1,5]]

Consultați imaginea de mai jos, în care am marcat dalele cu numerele corespunzătoare ale dalelor din datele de nivel:

Acum, că am înțeles conceptul de abordare pe bază de țiglă, permiteți-mi să vă arăt cum putem folosi un pseudo-cod simplu de grilă 2D pentru a face nivelul nostru:

pentru (i, buclă prin rânduri) pentru (j, buclă prin coloane) x = j * lățimea tilei y =

Dacă folosim imaginile de țiglă de mai sus, atunci lățimea plăcilor și înălțimea plăcilor sunt egale (și aceleași pentru toate plăcile) și se vor potrivi cu dimensiunile imaginilor. Deci, lățimea dalelor și înălțimea dalelor pentru acest exemplu sunt ambele 50 px, ceea ce reprezintă dimensiunea totală a nivelului de 300 x 300 px - adică șase rânduri și șase coloane de dale de 50 x 50 px fiecare.

Așa cum am discutat mai devreme, într-o abordare normală pe bază de țiglă, implementăm fie o vedere de sus în jos, fie o vedere laterală; pentru o perspectivă izometrică, trebuie să implementăm izometrie proiecție.

2. Proiecție izometrică

Cea mai bună explicație tehnică a ceea ce izometrie proiecție înseamnă, în măsura în care știu, din acest articol de Clint Bellanger:

Ne înclinăm camera pe două axe (rotiți camera cu 45 de grade pe o parte, apoi cu 30 de grade în jos). Acest lucru creează o grilă în formă de diamant (romb) în care spațiile de rețea sunt de două ori mai largi decât înălțime. Acest stil a fost popularizat prin jocuri de strategie și acțiuni RPG. Dacă privim un cub în această privință, sunt vizibile trei laturi (partea superioară și cele două fețe).

Deși sună puțin cam complicat, implementarea acestei perspective este foarte ușoară. Ceea ce trebuie să înțelegem este relația dintre spațiul 2D și spațiul izometric - adică relația dintre datele de nivel și vizualizare; transformarea de sus în jos cartezian coordonate cu coordonatele izometrice. Imaginea de mai jos arată transformarea vizuală:

Plasarea plăcilor izometrice

Permiteți-mi să încerc să simplific relația dintre datele de nivel stocate ca o matrice 2D și vizualizarea izometrică - adică modul în care transformăm coordonatele carteziene în coordonate izometrice. Vom încerca să creăm viziunea izometrică pentru pășunea noastră faimoasă acum zidită. Aplicația de vizualizare 2D a nivelului a fost o iterație simplă cu două bucle, plasând gresie pătrată compensând fiecare cu valorile înălțimii dalelor și a lățimii dalelor. Pentru vederea izometrică, codul pseudo rămâne același, dar placeTile () modificări ale funcțiilor.

Funcția inițială trage doar imaginile țiglelor la coordonatele furnizate X și y, dar pentru o vedere izometrică trebuie să calculam coordonatele izometrice corespunzătoare. Ecuațiile pentru a face acest lucru sunt după cum urmează izox și isoY reprezintă coordonatele izometrice x și y și cartX și Carty reprezintă coordonatele cartesian x și y:

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

Da, asta este. Aceste ecuații simple sunt proiecția izometrică din spatele magiei. Aici sunt funcții helper Phaser care pot fi folosite pentru a converti de la un sistem la altul folosind foarte convenabil Punct clasă:

funcția cartesianToIsometric (cartPt) var tempPt = nou Phaser.Point (); tempPt.x = cartPt.x-cartPt.y; tempPt.y = (cartPt.x + cartPt.y) / 2; retur (tempPt); 
funcția isometricToCartesian (isoPt) var tempPt = nou Phaser.Point (); tempPt.x = (2 * isoPt.y + isoPt.x) / 2; tempPt.y = (2 * isoPt.y-isoPt.x) / 2; retur (tempPt);  

Așa că putem folosi cartesianToIsometric metoda helper pentru a transforma coordonatele 2D de intrare în coordonate izometrice în interiorul placeTile metodă. În afară de aceasta, codul de randare rămâne același, dar trebuie să avem imagini noi pentru plăci. Nu putem folosi plăcile vechi pătrate folosite pentru redarea noastră de sus în jos. Imaginea de mai jos prezintă noile plăci de iarbă izometrice și pereți, împreună cu nivelul izometric ranit:

Incredibil, nu-i așa? Să vedem cum o poziție tipică 2D devine convertită într-o poziție izometrică:

Punct 2D = [100, 100]; // Punctul isometric se va calcula ca mai jos isoX = 100 - 100; // = 0 izoY = (100 + 100) / 2; // = 100 Punct Iso == [0, 100];

În mod similar, o intrare de [0, 0] va avea ca rezultat [0, 0], și [10, 5] va da [5, 7,5].

Pentru pajiștea noastră cu pereți, putem determina o zonă de pătrundere prin verificarea dacă elementul de matrice este 0 la acea coordonată, indicând astfel iarba. Pentru aceasta trebuie să determinăm coordonatele matricei. Putem găsi coordonatele dalelor în datele de nivel din coordonatele cartesiene folosind această funcție:

funcția getTileCoordinates (cartPt, tileHeight) var tempPt = nou Phaser.Point (); tempPt.x = Math.floor (cartPt.x / tileHeight); tempPt.y = Math.floor (cartPt.y / tileHeight); return (tempPt); 

(Aici, în esență, presupunem că înălțimea plăcilor și lățimea plăcilor sunt egale, ca în cele mai multe cazuri.) 

Prin urmare, dintr-o pereche de coordonate ecran (izometrice), putem gasi coordonatele de tigla sunand:

getTileCoordinates (isometricToCartesian (punct ecran), înălțimea plăcilor);

Acest punct de ecran ar putea fi, de exemplu, o poziție de clic-mouse sau o poziție de preluare.

Puncte de înregistrare

În Flash, am putea stabili puncte arbitrare pentru un grafic ca punct central sau [0,0]. Echivalentul Phaser este Pivot. Când plasați graficul la cuvânt [10,20], apoi asta Pivot punct va fi aliniat cu [10,20]. Implicit, colțul din stânga sus al unui grafic este considerat drept [0,0] sau Pivot. Dacă încercați să creați nivelul de mai sus folosind codul furnizat, atunci nu veți obține rezultatul afișat. În schimb, veți obține un teren plat fără pereți, ca mai jos:

Acest lucru se datorează faptului că imaginile țiglelor sunt de dimensiuni diferite și nu ne adresăm atributului de înălțime al plăcii de perete. Imaginea de mai jos arată imaginile diferite de țiglă pe care le folosim împreună cu casetele de delimitare și un cerc alb în care valoarea lor implicită [0,0] este:

Vedeți cum eroul devine nealiniat la desen, folosind pivoturile implicite. De asemenea, observați cum pierdem înălțimea plăcii de perete dacă este trasată folosind pivoți impliciți. Imaginea din dreapta arată cum trebuie să fie aliniate corespunzător, astfel încât plăcile de perete să se înalțe și eroul să fie plasat în mijlocul plăcilor de iarbă. Această problemă poate fi rezolvată în moduri diferite.

  1. Faceți toate plăcile în aceeași dimensiune a imaginii cu graficul aliniat corespunzător în imagine. Acest lucru creează o mulțime de zone goale în cadrul fiecărui grafic.
  2. Setați manual puncte pentru puncte pentru fiecare placă, astfel încât să se alinieze corect.
  3. Desenați plăci cu compensări specifice, astfel încât să se alinieze corect.

Pentru acest tutorial am ales să folosesc a treia metodă pentru ca aceasta să funcționeze chiar și cu un cadru fără capacitatea de a stabili puncte de pivotare.

3. Deplasarea în coordonate izometrice

Nu vom încerca niciodată să ne mișcăm direct caracterul sau proiectilul în coordonate izometrice. În schimb, vom manipula datele noastre despre lumea jocurilor în coordonate carteziene și vom folosi doar funcțiile de mai sus pentru actualizarea celor de pe ecran. De exemplu, dacă doriți să mutați un caracter înainte în direcția y pozitivă, puteți pur și simplu să îi creșteți y proprietăți în coordonate 2D și apoi convertiți poziția rezultată în coordonate izometrice:

y = y + viteza; placetile (cartesianToIsometric (noul Phaser.Point (x, y)))

Va fi un moment bun pentru a revizui toate noile concepte pe care le-am învățat până acum și pentru a încerca să creați un exemplu de lucru ce se mișcă într-o lume izometrică. Puteți găsi activele de imagine necesare în bunuri folder al depozitului sursă git.

Adâncimea de sortare

Dacă ați încercat să mutați imaginea mingii în grădina noastră cu pereți, atunci veți întâlni problemele sortarea în profunzime. În plus față de plasarea normală, va trebui să avem grijă sortarea în profunzime pentru desenarea lumii isometrice, dacă există elemente în mișcare. Adunarea corectă a adâncimii vă asigură că obiectele mai aproape de ecran sunt desenate în partea de sus a elementelor mai îndepărtate.

Cea mai simplă metodă de sortare a adâncimii este pur și simplu să utilizați valoarea coordonatei cartesiene y, așa cum se menționează în acest Sfat rapid: cu cât este mai mare obiectul pe ecran, cu atât ar trebui să fie desenată mai devreme. Acest lucru poate funcționa bine pentru scenele izometrice foarte simple, dar o modalitate mai bună va fi redesenarea scenei isometrice odată ce se va întâmpla o mișcare, în funcție de coordonatele matricei plăcii. Permiteți-mi să explic acest concept în detaliu cu codul nostru pseudo pentru desenul de nivel:

pentru (i, buclă prin rânduri) pentru (j, buclă prin coloane) x = j * lățimea tilei y =

Imaginați-vă că elementul sau caracterul nostru se află pe țiglă [1,1]-adică cea mai înaltă țiglă verde din perspectiva izometrică. Pentru a trage în mod corespunzător nivelul, caracterul trebuie să fie desenat după desenarea plăcii de perete din colț, atât plăcile de perete din stânga, cât și din dreapta, precum și țiglele de pământ, după cum urmează:

Dacă urmăm buclă de tragere conform pseudo-codului de mai sus, vom trage mai întâi peretele colțului din mijloc și apoi vom continua să tragem toți pereții în partea din dreapta sus până când ajunge în colțul din dreapta. 

Apoi, în următoarea buclă, se va desena peretele din stânga caracterului, iar apoi pe iarba pe care se află caracterul. Odată ce vom determina că aceasta este piesa care ocupă caracterul nostru, vom desena caracterul după desenarea plăcilor de iarbă. În acest fel, dacă ar fi existat pereți pe cele trei plăci de iarbă libere conectate la cel pe care este în picioare caracterul, acești pereți se vor suprapune caracterului, rezultând o redare corectă a adâncilor.

4. Crearea artei

Arta izometrică poate fi arta pixelilor, dar nu trebuie să fie. Atunci când se ocupă de arta pixel izometric, ghidul RhysD vă spune aproape tot ce trebuie să știți. Unele teorii pot fi găsite și pe Wikipedia.

La crearea artei izometrice, regulile generale sunt:

  • Începeți cu o grilă izometrică goală și respectați precizia perfectă a pixelilor.
  • Încercați să descompuneți arta în imagini singulare izometrice.
  • Încercați să vă asigurați că fiecare placă este fie walkable sau non-walkable. Va fi complicat dacă avem nevoie să găzduim o singură țiglă care conține atât zone în mișcare, cât și zone care nu se află în mișcare.
  • Cele mai multe plăci vor trebui să se potrivească perfect în una sau mai multe direcții.
  • Umbrele pot fi dificil de implementat, cu excepția cazului în care folosim o abordare stratificată în care atragem umbre pe stratul de bază și apoi tragem eroul (sau copacii sau alte obiecte) pe stratul superior. Dacă abordarea pe care o utilizați nu este multistrat, asigurați-vă că umbrele cad în față, astfel încât să nu cadă, să zicem, eroul atunci când stă în spatele unui copac.
  • În cazul în care trebuie să utilizați o imagine de țiglă mai mare decât dimensiunea standard a plăcuței izometrice, încercați să utilizați o dimensiune care este mai mare decât mărimea mărimii izolate. Este mai bine să avem o abordare stratificată în astfel de cazuri, unde putem împărți arta în bucăți diferite pe baza înălțimii ei. De exemplu, un copac poate fi împărțit în trei bucăți: rădăcina, trunchiul și frunzele. Acest lucru face mai ușor să se adune adâncimi, deoarece putem desena piesele în straturi corespunzătoare care corespund înălțimilor lor.

Placile izometrice care sunt mai mari decât dimensiunile unui singur tiglă vor crea probleme cu sortarea în adâncime. Unele aspecte sunt discutate în aceste linkuri:

postări asemănatoare

  • Placi mai mari
  • Algoritmul Splitting și Painter
  • Postul OpenSpace pe căi eficiente de împărțire a plăcilor mai mari

5. Caracterele isometrice

În primul rând, vom avea nevoie să stabilim câte direcții de mișcare sunt permise în jocul nostru - de obicei, jocurile vor oferi mișcare în patru sensuri sau mișcare în opt moduri. Consultați imaginea de mai jos pentru a înțelege corelația dintre spațiul 2D și spațiul izometric:

Rețineți că un personaj se va mișca vertical când apăsăm Săgeata în sus tastați într-un joc de sus în jos, dar pentru un joc izometric caracterul se va deplasa la un unghi de 45 de grade spre colțul din dreapta sus. 

Pentru o vizualizare de sus în jos, am putea crea un set de animații de caractere care se confruntă într-o singură direcție și pur și simplu le rotim pentru toate celelalte. Pentru arta caracterului izometric, trebuie să redimensionăm fiecare animație în fiecare dintre direcțiile permise - deci pentru mișcare cu opt direcții, trebuie să creăm opt animații pentru fiecare acțiune. 

Pentru ușurința înțelegerii, de obicei indicăm direcțiile ca Nord, Nord-Vest, Vest, Sud-Vest, Sud, Sud-Est, Est și Nord-Est. Cadrele de caractere de mai jos arată cadre inactive pornind de la sud-est și mergând în sensul acelor de ceasornic:

Vom plasa caractere în același mod în care am plasat dale. Mișcarea unui personaj se realizează prin calcularea mișcării în coordonate carteziene și apoi prin transformarea în coordonate izometrice. Să presupunem că folosim tastatura pentru a controla caracterul.

Vom stabili două variabile, dX și dY, pe baza tastelor direcționale presate. Implicit, aceste variabile vor fi 0 și va fi actualizat conform graficului de mai jos, unde UDR, și L desemnează SusJosDreapta, și Stânga tastele săgeată, respectiv. O valoare de 1 sub o cheie reprezintă faptul că cheia este presată; 0 implică faptul că cheia nu este apăsată.

 Key Pos UDRL dX dY = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -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

Acum, folosind valorile dX și dY, putem actualiza coordonatele carteziene cum ar fi:

newX = curentX + (viteza dX *); newY = curentY + (viteza dY *);

Asa de dX și dY reprezintă schimbarea pozițiilor x și y ale personajului, pe baza tastelor apăsate. Putem calcula cu ușurință coordonatele izometrice noi, după cum am discutat deja:

Iso = cartesianToIsometric (nou Phaser.Point (newX, newY))

Odată ce avem noua poziție izometrică, trebuie să mișcare caracterul în această poziție. Pe baza valorilor pe care le avem dX și dY, putem decide în ce direcție se confruntă caracterul și să utilizeze arta caracterului corespunzător. Odată ce caracterul este mutat, vă rugăm să nu uitați să restabiliți nivelul cu sortarea adecvată a adâncimii, deoarece coordonatele de dale ale personajului s-ar fi schimbat.

Detectarea coliziunii

Detectarea coliziunilor se face verificând dacă placa de la poziția nou calculată este o piesă care nu se învârte. Deci, odată ce găsim noua poziție, nu mutăm imediat personajul acolo, dar verificăm mai întâi pentru a vedea ce dale ocupă acel spațiu.

tile coordinate = getTileCoordinates (isometricToCartesian (poziția curentă), înălțimea plăcilor); dacă (isWalkable (coordonate țiglă)) moveCharacter ();  altfel // nu face nimic; 

În funcție isWalkable (), verificăm dacă valoarea matricei de date la nivelul coordonatei date este o țiglă sau nu. Trebuie să avem grijă să actualizăm direcția în care se confruntă caracterul-chiar dacă nu se mișcă, ca și în cazul în care el lovește o țiglă care nu se învârte.

Acum, aceasta poate părea o soluție corectă, dar va funcționa numai pentru elementele fără volum. Acest lucru se datorează faptului că luăm în considerare doar un singur punct, care este punctul central al personajului, pentru a calcula coliziunea. Ceea ce trebuie să facem este să găsim toate cele patru colțuri ale personajului din coordonatele punctului 2D disponibil și să calculați coliziunile pentru toate acestea. Dacă un colț intră într-o țiglă care nu se învârte, atunci nu ar trebui să ne mișcăm caracterul.

Adâncimea de sortare cu caractere

Luați în considerare un caracter și o țiglă de copaci în lumea izometrică, și ei ambele au aceleași dimensiuni ale imaginii, oricât de nerealist ar suna.

Pentru a înțelege corect sortarea adâncimii, trebuie să înțelegem că ori de câte ori coordonatele x și y ale caracterului sunt mai mici decât cele ale arborelui, arborele se suprapune caracterului. Ori de câte ori coordonatele caracterului x și y sunt mai mari decât cele ale arborelui, caracterul se suprapune cu arborele. Când au aceeași coordonată x, atunci vom decide doar pe baza coordonatei y: oricare are cea mai mare coordonată y se suprapune pe cealaltă. Atunci când au aceeași coordonată y, atunci vom decide doar pe baza coordonatei x: oricare dintre ele are coordonatele x mai mari, se suprapune celălalt.

Așa cum am explicat mai devreme, o versiune simplificată a acestui lucru este doar pentru a desena nivelurile începând de la cea mai îndepărtată țiglă - adică, țiglă [0] [0]-și apoi trageți toate plăcile în fiecare rând unul câte unul. Dacă un personaj ocupă o țiglă, tragem mai întâi țiglă și apoi facem țiglă de caractere. Acest lucru va funcționa bine, deoarece personajul nu poate ocupa o placă de perete.

6. Timp demo!

Acesta este un demo în Phaser. Faceți clic pentru a vă concentra pe zona interactivă și utilizați tastele săgeată pentru a muta caracterul. Puteți utiliza două săgeți pentru a vă deplasa în direcțiile diagonale.

Puteți găsi sursa completă pentru demo-ul din depozitul sursă pentru acest tutorial.