În acest tutorial, vă voi oferi o privire de ansamblu asupra a ceea ce trebuie să știți pentru a crea lumi isometrice. Veți afla ce este proiecția izometrică și cum să reprezentați nivele izometrice ca matrice 2D. Vom formula relații între vizualizare și logică, astfel încât să putem manipula cu ușurință obiecte pe ecran și să ne ocupăm de detectarea coliziunilor pe bază de dale. Vom analiza, de asemenea, sortarea în profunzime și animația personajelor.
Pentru a ajuta la accelerarea dezvoltării jocului, puteți găsi o gamă largă de elemente de joc izometrice pe Envato Market, gata de utilizare în joc.
Jocuri izometrice pe jocul Envato Market postări asemănatoareVrei mai multe sfaturi despre crearea lumilor isometrice? Verificați postul de urmărire, Crearea izometricilor: Un primator pentru Gamedevs, Continuat și cartea lui Juwal, Esselles Starling Game Development.
Vedere izometrică este o metodă de afișare utilizată pentru a crea o iluzie a 3D pentru un joc 2D altfel - denumit uneori și pseudo 3D sau 2.5D. Aceste imagini (luate din Diablo 2 și Age of Empires) ilustrează ce vreau să spun:
Diablo 2 Vârsta imperiilorPunerea în aplicare a unei viziuni izometrice se poate face în mai multe moduri, dar din motive de simplitate mă voi concentra asupra a țiglă pe bază de , care este cea mai eficientă și mai răspândită metodă. Am suprapus fiecare captură de ecran de mai sus cu o rețea de diamante care arată modul în care terenul este împărțit în plăci.
În 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 2D.
postări asemănatoareDe exemplu, să luăm în considerare o vedere standard 2D de sus în jos cu două plăci - o țiglă și o țiglă - așa cum se arată aici:
Unele plăci simpleAceste plăci sunt fiecare de aceeași dimensiune ca fiecare alte și sunt fiecare pătrate, astfel încât înălțimea plăcilor și lățimea plăcilor sunt aceleași.
Pentru un nivel cu pășune închisă pe toate părțile de pereți, matricea 2D a nivelului de date va 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 dalelor în funcție de datele de nivel va produce imaginea de nivel inferior:
Putem imbunatati acest lucru prin adaugarea de placi de colt si placi de pereti verticale si orizontale separate, care necesita cinci placi suplimentare:
[[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]]Nivel îmbunătățit cu numere de țiglă
Sper că conceptul abordării pe bază de țiglă este acum clar. Aceasta este o implementare simplă a rețelei 2D, pe care o putem codifica astfel:
pentru (i, buclă prin rânduri) pentru (j, buclă prin coloane) x = j * lățimea tilei y =
Aici presupunem că lățimea plăcilor și înălțimea plăcilor sunt egale (și aceleași pentru toate plăcile) și se potrivesc cu dimensiunile imaginilor țiglelor. Astfel, lățimea plăcilor și înălțimea plăcilor pentru acest exemplu sunt ambele de 50 pixeli, ceea ce reprezintă dimensiunea totală de 300 x 300 pixeli - adică șase rânduri și șase coloane de dale de câte 50 x 50 pixeli fiecare.
Într-o abordare normală bazată pe ț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.
Cea mai bună explicație tehnică a ceea ce înseamnă "proiecție izometrică" înseamnă, din câte ș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ă un pic cam complicat, punerea în practică a acestei viziuni este simplă. 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 vedere; transformarea de la coordonatele "cartesiene" de sus în jos la coordonatele izometrice.
Grila carteziană vs. grila izometrică.(Nu luăm în considerare o tehnică bazată pe plăci hexagonale, care este un alt mod de a implementa lumi izometrice.)
Permiteți-mi să încerc să simplific relația dintre datele de nivel stocate ca o matrice 2D și vederea izometrică - adică modul în care transformăm coordonatele carteziene în coordonate izometrice.
Vom încerca să creăm perspectiva izometrică pentru datele privind nivelul pajiștilor închise pe pereți:
[[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]
În acest scenariu putem determina o zonă care poate fi accesată prin verificarea elementului matricei 0
la acea coordonată, indicând astfel iarba. Aplicarea vizualizării 2D a nivelului de mai sus a fost o iterație simplă cu două bucle, plasând gresie pătrată compensând fiecare cu înălțimea fixă a dalelor și a lățimii dalelor.
pentru (i, buclă prin rânduri) pentru (j, buclă prin coloane) x = j * lățimea tilei y =
Pentru vederea izometrică, codul rămâne același, dar placeTile ()
modificări ale funcțiilor.
Pentru o vedere izometrică trebuie să calculați coordonatele izometrice corespunzătoare în interiorul buclelor.
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;
Aceste funcții arată modul în care puteți converti de la un sistem la altul:
funcția isoTo2D (pt: Point): Punctul var tempPt: Point = new Point (0, 0); tempPt.x = (2 * pt.y + pt.x) / 2; tempPt.y = (2 * pt.y - pt.x) / 2; return (tempPt);
funcția twoDToIso (pt: Point): Punctul var tempPt: Point = new Point (0,0); tempPt.x = pt.x - pt.y; tempPt.y = (pt.x + pt.y) / 2; return (tempPt);
Pseudocodul pentru bucla arata astfel:
pentru (i, buclă prin rânduri) pentru (j, buclă prin coloane) x = j * lățimea dalelor y = ))Pajiștea noastră închisă pe perete într-o vedere izometrică.
De exemplu, să vedem cum se convertește o poziție tipică 2D într-o poziție izometrică:
Punct 2D = [100, 100]; // twoDToIso (punctul 2D) va fi calculat 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]
.
Metoda de mai sus ne permite să creăm o corelație directă între datele de nivel 2D și coordonatele izometrice. Putem găsi coordonatele dalelor în datele de nivel din coordonatele cartesiene folosind această funcție:
funcția getTileCoordinates (pt: Point, tileHeight: Number): Punctul var tempPt: Point = new Point (0, 0); tempPt.x = Math.floor (pt.x / tileHeight); tempPt.y = Math.floor (pt.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 (isoTo2D (punct de 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.
Bacsis: O altă metodă de plasare este modelul Zigzag, care are o abordare diferită.
Mișcarea este foarte ușoară: manipulezi datele din lumea jocurilor în coordonate carteziene și folosești doar funcțiile de mai sus pentru a le actualiza 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
proprietate și apoi convertiți poziția sa la coordonatele izometrice:
y = y + viteza; placetile (două puncte) (nou punct (x, y)))
În plus față de plasarea normală, va trebui să avem grijă sortarea în profunzime pentru desenarea lumii isometrice. Acest lucru vă asigură că articolele mai aproape de player sunt desenate în partea superioară 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 funcționează atât timp cât nu avem sprite care ocupă mai mult decât un singur spațiu de țiglă.
Cea mai eficientă modalitate de sortare a adâncimilor pentru lumile izometrice este de a rupe toate plăcile în dimensiuni standard cu o singură țiglă și de a nu permite imagini mai mari. De exemplu, aici este o faianță care nu se încadrează în dimensiunea standard a plăcilor - a se vedea cum o putem împărți în mai multe plăci care corespund fiecărei dimensiuni:
O imagine mare este împărțită în mai multe plăci de dimensiuni izometrice standardArta 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.
Când se creează artă izometrică, regulile generale sunt
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ănatoarePunerea în aplicare a caracterelor în vederea izometrică nu este complicată, deoarece ar putea suna. Arta artelor trebuie să fie creată în conformitate cu anumite standarde. În primul rând, va trebui să stabilim câte direcții de mișcare sunt permise în jocul nostru - de obicei jocurile vor oferi mișcare în patru direcții sau mișcare în opt moduri.
Direcții de navigare cu opt direcții în vizualizări de sus în jos și izometrice.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 caracteristică izometrică, trebuie să redimensionăm fiecare animație în fiecare dintre direcțiile permise - deci pentru mișcarea în 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, în sensul acelor de ceasornic.
Un caracter izometric care se confruntă în direcții diferite.Am plasat caractere în același mod în care plasăm 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 U
, D
, R
și L
desemnează Sus, Jos, Dreapta și Stânga tastele săgeată, respectiv. O valoare de 1
sub o cheie reprezintă acea cheie care 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 astfel:
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 = 2DToIso (nou punct (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.
Detectarea coliziunilor se face verificând dacă placa din 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 (isoTo2D (izo punct), î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.
Luați în considerare un caracter și o țiglă de copaci în lumea izometrică.
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 copacului, 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 coordonate y, atunci decidem numai pe baza coordonatei x: oricare ar avea coordonatele x mai mari, se suprapune celălalt.
O versiune simplificată a acestui lucru este de a trage doar secvențial nivelurile începând de la cea mai îndepărtată țiglă - adică, țiglă [0] [0]
- 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.
Adunarea în adâncime trebuie efectuată de fiecare dată când orice piesă modifică poziția. De exemplu, trebuie să o facem ori de câte ori personajele se mișcă. Apoi, actualizăm scena afișată, după ce efectuăm sortarea de adâncime, pentru a reflecta modificările adâncimii.
Acum, puneți-vă noile cunoștințe la o bună utilizare, creând un prototip de lucru, cu comenzi de la tastatură și cu sortarea corectă a adâncimii și detectarea coliziunilor. Iată demo-ul meu:
Faceți clic pentru a da focalizarea SWF, apoi utilizați tastele săgeți. Faceți clic aici pentru versiunea completă.
Puteți găsi această clasă de utilitate utilă (am scris-o în AS3, dar ar trebui să o puteți înțelege în orice altă limbă de programare):
pachet com.csharks.juwalbose import flash.display.Sprite; import flash.geom.Point; clasa publică IsoHelper / ** * convertiți un punct izometric la 2D * * / funcția publică statică isoTo2D (pt: Point): Punct // gx = (2 * isoy + isox) / 2; // gy = (2 * izoy-izox) / 2 var tempPt: Punct = nou punct (0,0); tempPt.x = (2 * pt.y + pt.x) / 2; tempPt.y = (2 * pt.y-pt.x) / 2; return (tempPt); / ** * convertiți un punct 2d la izometric * * / funcția statică publică twoDToIso (pt: Point): Punct // gx = (isox-isoxy; // gy = (isoy + isox) / 2 var tempPt: = nou punct (0,0); tempPt.x = pt.x-pt.y; tempPt.y = (pt.x + pt.y) / 2; retur (tempPt); / ** * converti un 2d (pt: Point, tileHeight: Number): Punctul var tempPt: Point = nou punct (0,0); tempPt.x = Math.floor (pt.x / tileHeight); tempPt.y = Math.floor (pt.y / tileHeight); întoarcere (tempPt); / ** * conversia rândului / coloanei specifice punctului 2d * * / funcția statică publică get2dFromTileCoordinates (pt: tileHeight: Number): Punctul var tempPt: Point = punct nou (0,0); tempPt.x = pt.x * tileHeight; tempPt.y = pt.y * tileHeight; return (tempPt);
Dacă sunteți foarte blocat, iată codul complet din demo-ul meu (în formularul de cod temporal pentru Flash și AS3):
// Utilizează clasa KeyObject a lui 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; importul 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 nivelData = [[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]]; var tileLățime: uint = 50; var borderOffsetY: uint = 70; var borderOffsetX: uint = 275; var față: String = "sud"; var currentFacing: String = "sud"; er eroul: MovieClip = erotica nouă (); hero.clip.gotoAndStop (care se confruntă); var heroPointer: Sprite; var cheie: KeyObject = new KeyObject (stadiu); // Senocular KeyObject Class var heroHalfSize: uint = 20; // placile var grassTile: MovieClip = nou TileMc (); grassTile.gotoAndStop (1); var wallTile: MovieClip = nou TileMc (); wallTile.gotoAndStop (2); // panza var bg: Bitmap = Bitmap nouă (nouă BitmapData (650,450)); addChild (bg); var rect: dreptunghi = bg.bitmapData.rect; // să se ocupe de adâncime var overlayContainer: Sprite = Sprite nou (); addChild (overlayContainer); // pentru a manipula direcția de deplasare var dX: Number = 0; var dY: Număr = 0; var idle: Boolean = adevărat; var viteza: uint = 5; var heroCartPos: Punct = nou punct (); var heroTile: Punct = nou punct (); // adăugați elemente la nivel de start, adăugați funcția buclă de joc createLevel () var tileType: uint; pentru (var i: uint = 0; iPuncte de înregistrare
Acordați o atenție deosebită punctelor de înregistrare ale plăcilor și eroului. (Punctele de înregistrare pot fi considerate ca fiind puncte de origine pentru fiecare sprite special.) Acestea, în general, nu vor cădea în interiorul imaginii, ci mai degrabă va fi colțul din stânga sus al casetei de legare a spritei.
Va trebui să modificăm codul de desen pentru a corecta corect punctele de înregistrare, în special pentru erou.
Detectarea coliziunii
Un alt punct interesant de observat este faptul că se calculează detecția coliziunilor pe baza punctului în care este eroul.
Dar eroul are volum și nu poate fi reprezentat cu precizie de un singur punct, așa că trebuie să reprezentăm eroul ca un dreptunghi și să verificăm coliziuni împotriva fiecărui colț al acestui dreptunghi, astfel încât să nu existe suprapuneri cu alte plăci și deci nu artefacte de adâncime.
Comenzi rapide
În demo, redesenționez scena din nou pe fiecare cadru pe baza noii poziții a eroului. Gasim tigla pe care eroul o ocupa si atrage eroul deasupra piesei de pamant atunci cand buclele de randare ajung in acele gresie.
Dar, dacă ne uităm mai aproape, vom constata că nu este nevoie să rupem prin toate plăcile în acest caz. Plăcile de iarbă și plăcile de perete de sus și de stânga sunt întotdeauna desenate înainte ca eroul să fie desenat, deci nu trebuie să le redresăm deloc. De asemenea, plăcile de perete din partea de jos și din dreapta sunt întotdeauna în fața eroului și, prin urmare, desenate după eroul este desenat.
În esență, atunci avem nevoie doar de a efectua sortarea adâncimilor între peretele din interiorul zonei active și eroul - adică două plăci. Observând aceste tipuri de comenzi rapide vă va ajuta să economisiți o mulțime de timp de procesare, care poate fi crucială pentru performanță.
Concluzie
Până acum, ar trebui să aveți o bază excelentă pentru a construi propriile jocuri izometrice: puteți să redați lumea și obiectele din ea, să reprezentați date de nivel în mese simple 2D, să convertiți între coordonate carteziene și izometrice și să vă ocupați de concepte cum ar fi sortarea adâncimii și animație de caractere. Bucurați-vă de crearea unor lumi isometrice!
postări asemănatoare
- Crearea de lumi isometrice: un element de bază pentru Gamedev, continuat
- Sfat rapid: Nivele izometrice ușor 'n' ușor
- Matematica izometrică
- 6 Ghiduri incredibil de aprofundate pentru dezvoltarea jocului și design pentru începători