În articolul meu precedent, ne-am uitat la Tiled Map Editor ca un instrument pentru a face niveluri pentru jocurile tale. În acest tutorial, vă voi trece prin următorul pas: parsarea și redarea acestor hărți în motorul dvs..
Notă: Deși acest tutorial este scris folosind Flash și AS3, ar trebui să puteți utiliza aceleași tehnici și concepte în aproape orice mediu de dezvoltare a jocului.
Utilizând specificația TMX, putem stoca datele într-o varietate de moduri. Pentru acest tutorial vom salva hărțile noastre în format XML. Dacă intenționați să utilizați fișierul TMX inclus în secțiunea de cerințe, puteți trece la secțiunea următoare.
Dacă ați realizat propria hartă, va trebui să le spuneți Tiled pentru ao salva ca XML. Pentru aceasta, deschideți harta cu Placi și selectați Editare> Preferințe ...
Pentru caseta drop-down "Stocați datele din stratul de țiglă ca:", selectați XML, după cum se arată în imaginea de mai jos:
Acum, când salvați harta, aceasta va fi stocată în format XML. Simțiți-vă liber să deschideți fișierul TMX cu un editor de text pentru a vă privi în interior. Iată un fragment din ceea ce vă puteți aștepta să găsiți:
După cum puteți vedea, stochează pur și simplu toate informațiile de hartă în acest format XML la îndemână. Proprietățile ar trebui să fie în mare parte simple, cu excepția gid
- Voi face o explicație mai aprofundată a acestui lucru mai târziu în tutorial.
Înainte de a merge mai departe, aș vrea să vă îndrept atenția asupra objectgroup
"Coliziune
"După cum ați putea reține din tutorialul de creare a hărții, am specificat zona de coliziune din jurul copacului, așa este stocat.
Puteți specifica în același mod puterile sau punctul de joc al jucătorului, astfel încât să vă puteți imagina câte posibilități există pentru Placi ca editor de hărți!
Iată o scurtă prezentare a modului în care vom primi harta noastră în joc:
În ceea ce privește programul dvs., acesta este doar un fișier XML, deci primul lucru pe care îl facem este să îl citiți. Majoritatea limbilor au o bibliotecă XML pentru aceasta; în cazul AS3 voi folosi clasa XML pentru a stoca informația XML și un URLLoader pentru a citi în fișierul TMX.
xmlLoader = noul URLLoader (); xmlLoader.addEventListener (Event.COMPLETE, xmlLoadComplete); xmlLoader.load (noua adresă URLRequest ("... /assets/example.tmx"));
Acesta este un cititor de fișiere simplu pentru "... /assets/example.tmx"
. Se presupune că fișierul TMX este localizat în directorul dvs. de proiect în dosarul "active". Avem nevoie doar de o funcție care să se ocupe de citirea fișierului:
funcția privată xmlLoadComplete (e: Event): void xml = XML nou (e.target.data); mapWidth = xml.attribute ("lățime"); mapHeight = xml.attribute ("înălțime"); tileWidth = xml.attribute ("tilewidth"); tileHeight = xml.attribute ("tileheight"); var xmlCounter: uint = 0; pentru fiecare (var tileset: XML în xml.tileset) var imageWidth: uint = xml.tileset.image.attribute ("lățime") [xmlCounter]; var imageHeight: uint = xml.tileset.image.attribute ("înălțime") [xmlCounter]; var primulGid: uint = xml.tileset.attribute ("firstgid") [xmlCounter]; var tilesetName: String = xml.tileset.attribute ("nume") [xmlCounter]; var tilesetTileWidth: uint = xml.tileset.attribute ("tilewidth") [xmlCounter]; var tilesetTileHeight: uint = xml.tileset.attribute ("tileheight") [xmlCounter]; var tilesetImagePath: String = xml.tileset.image.attribute ("sursă") [xmlCounter]; tileSets.push (noul TileSet (primulGid, tilesetName, tilesetTileWidth, tilesetTileHeight, tilesetImagePath, imageWidth, imageHeight)); xmlCounter ++; totalTileSets = xmlCounter;
Aici se desfășoară parsarea inițială. (Există câteva variabile pe care le vom urmări în afara acestei funcții, deoarece le vom folosi mai târziu.)
Odată ce avem datele de hartă stocate, vom trece la parsarea fiecărui tileset. Am creat o clasă pentru a stoca informațiile despre fiecare tileset. Vom împinge fiecare dintre aceste instanțe de clasă într-o matrice, deoarece le vom folosi mai târziu:
clasa publică TileSet public var firstgid: uint; public var lastgid: uint; numele public var: String; public var tileLățime: uint; public var sursa: String; public var tileHeight: uint; public var imageWidth: uint; public var imageHeight: uint; public var bitmapData: BitmapData; public var tileAmountWidth: uint; funcția publică TileSet (firstgid, name, tileWidth, tileHeight, source, imageWidth, imageHeight) this.firstgid = firstgid; this.name = nume; this.tileWidth = Lățime de tigla; this.tileHight = tileHeight; this.source = source; this.imageWidth = imageWidth; this.imageHeight = imageHeight; tileAmountWidth = Math.floor (imagineWidth / tileWidth); lastgid = țintăAmountWidth * Math.floor (imagineHeight / tileHeight) + firstgid - 1;
Din nou, puteți vedea asta gid
apare din nou, în firstgid
și lastgid
variabile. Să vedem acum ce este pentru asta.
gid
"Pentru fiecare țiglă, trebuie să o asociem într-un fel cu un tileset și cu o locație specială pe acel tileset. Acesta este scopul gid
.
Uită-te la iarbă-dale-2-small.png
tileset. Acesta conține 72 de plăci distincte:
Dăm fiecăruia dintre aceste dale unic gid
de la 1-72, astfel încât să putem face referire la unul cu un singur număr. Cu toate acestea, formatul TMX specifică doar primul gid
din tileset, din moment ce toate celelalte gid
s pot fi derivate din cunoașterea dimensiunii tilelor și a mărimii fiecărei plăci individuale.
Iată o imagine utilă pentru a ajuta la vizualizarea și explicarea procesului.
Deci, dacă aș fi așezat țigla din partea de jos a acestui tileset pe o hartă undeva, am stoca gid
72 în acea locație de pe hartă.
Acum, în exemplul fișierului TMX de mai sus, veți observa că tree2-final.png
are o firstgid
de 73. Asta pentru că continuăm să numărăm gid
s și nu o resetăm la 1 pentru fiecare tileset.
Pe scurt, a gid
este un ID unic dat fiecărei plăci din fiecare tileset dintr-un fișier TMX, în funcție de poziția plăcii din tileset și de numărul de tileseturi la care se face referire în fișierul TMX.
Acum vrem să încărăm toate imaginile sursă tileset în memorie, astfel încât să putem pune harta noastră împreună cu ei. Dacă nu scrieți acest lucru în AS3, singurul lucru pe care trebuie să îl cunoașteți este că încărcăm imaginile pentru fiecare tileset aici:
// încărcați imaginile pentru tileset pentru (var i = 0; i < totalTileSets; i++) var loader = new TileCodeEventLoader(); loader.contentLoaderInfo.addEventListener(Event.COMPLETE, tilesLoadComplete); loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, progressHandler); loader.tileSet = tileSets[i]; loader.load(new URLRequest("… /assets/" + tileSets[i].source)); eventLoaders.push(loader);
Există câteva lucruri specifice AS3 care se întâmplă aici, cum ar fi utilizarea clasei Loader pentru a aduce imaginile tileset. (Mai exact, este un termen extins Încărcător
, pur și simplu pentru a putea stoca TileSet
în fiecare interior Încărcător
. Acest lucru este astfel încât atunci când încărcătorul completează putem corela cu ușurință încărcătorul cu tileset.)
Acest lucru poate suna complicat, dar codul este într-adevăr destul de simplu:
clasa publică TileCodeEventLoader extinde Loader public var tileSet: TileSet;
Acum, înainte de a începe să luăm aceste tile și să le creăm cu ele, trebuie să creăm o imagine de bază pentru a le pune pe ele:
screenBitmap = Bitmap nou (nouă BitmapData (mapWidth * tileWidth, mapHeight * tileHeight, false, 0x22ffff)); screenBitmapTopLayer = bitmap nou (nouă BitmapData (mapWidth * tileWidth, mapHeight * tileHeight, true, 0));
Vom copia datele de pe aceste imagini bitmap astfel încât să le putem folosi ca fundal. Motivul pentru care am creat două imagini este așa încât să putem avea un strat superior și un strat inferior și să-l mutăm pe jucător între ele pentru a oferi perspectivă. De asemenea, specificăm că stratul superior ar trebui să aibă un canal alfa.
Pentru cei care ascultă evenimentele pentru încărcătoare putem folosi acest cod:
funcția privată progressHandler (eveniment: ProgressEvent): void trace ("progressHandler: bytesLoaded =" + event.bytesLoaded + "bytesTotal =" + event.bytesTotal);
Aceasta este o funcție distractivă deoarece puteți urmări cât de departe a încărcat imaginea și, prin urmare, poate oferi feedback utilizatorului cu privire la cât de repede se întâmplă lucrurile, cum ar fi o bară de progres.
funcții private de funcțiiLoadComplete (e: Event): void var currentTileset = e.target.loader.tileSet; currentTileset.bitmapData = Bitmap (e.target.content) .bitmapData; tileSetsLoaded ++; // așteptați până când toate imaginile tileset sunt încărcate înainte de a le combina stratul după strat într-un bitmap dacă (tileSetsLoaded == totalTileSets) addTileBitmapData ();
Aici stocăm datele bitmap cu tilesetul asociat cu acesta. De asemenea, contorizăm câte seturi de tile au încărcat complet, iar când s-au terminat, putem apela o funcție (am numit-o addTileBitmapData
în acest caz) pentru a începe punerea pieselor împreună.
Pentru a combina plăcile într-o singură imagine, dorim să construim stratul după strat astfel încât acesta să fie afișat la fel ca și fereastra de previzualizare din Tiling.
Iată cum va arăta funcția finală; comentariile pe care le-am inclus în codul sursă ar trebui să explice în mod adecvat ce se întâmplă fără a fi prea murdar în detalii. Trebuie să remarcăm că acest lucru poate fi implementat în multe moduri diferite, iar implementarea dvs. poate să arate complet diferită decât a mea.
funcția privată addTileBitmapData (): void // încărcați fiecare strat pentru fiecare (var strat: XML în xml.layer) var tiles: Array = Array nou (); var tileLungime: uint = 0; // atribuie gid fiecărei locații din stratul pentru fiecare (var tile: XML în layer.data.tile) var gid: Number = tile.attribute ("gid"); // dacă gid> 0 dacă (gid> 0) tiles [tileLength] = gid; tileLength ++; // pentru buclă continuă în fragmentele următoare
Ce se întâmplă aici este că analizăm numai dalele gid
s care sunt peste 0, deoarece 0 indică o țiglă goală și stocarea într-o matrice. Deoarece în stratul superior sunt atât de multe "dale", ar fi ineficient să le stocăm în memorie. Este important să rețineți că stocăm locația gid
cu un contor, deoarece vom folosi indexul său în matrice mai târziu.
var useBitmap: BitmapData; var stratName: String = layer.attribute ("nume") [0]; // decide unde vom pune stratul var layerMap: int = 0; comutator (layerName) caz "Top": layerMap = 1; pauză; implicit: urmărire ("folosind stratul de bază");
În această secțiune, analizăm numele stratului și verificăm dacă acesta este egal cu "Top". Dacă este cazul, setăm un drapel, astfel încât știm să-l copiem pe stratul de top bitmap. Putem fi foarte flexibili cu funcții de acest gen și putem folosi chiar mai multe straturi aranjate în orice ordine.
// stocați gid-ul într-o array 2d var tileCoordonate: Array = Array nou (); pentru (var tileX: int = 0; tileX < mapWidth; tileX++) tileCoordinates[tileX] = new Array(); for (var tileY:int = 0; tileY < mapHeight; tileY++) tileCoordinates[tileX][tileY] = tiles[(tileX+(tileY*mapWidth))];
Acum, aici stocăm gid
, pe care am analizat-o la început, într-o matrice 2D. Veți observa inițializările matricei duble; aceasta este pur și simplu o modalitate de a manipula matrice 2D în AS3.
Există și un fel de matematică. Amintiți-vă când am inițializat gresie
array de sus și cum am păstrat indexul cu el? Vom folosi acum indexul pentru a calcula coordonatele pe care le face gid
aparține lui. Această imagine demonstrează ce se întâmplă:
Deci, pentru acest exemplu, obținem gid
la indexul 27 în gresie
și păstrați-l la tileCoordinates [7] [1]
. Perfect!
pentru (var spriteForX: int = 0; spriteForX < mapWidth; spriteForX++) for (var spriteForY:int = 0; spriteForY < mapHeight; spriteForY++) var tileGid:int = int(tileCoordinates[spriteForX][spriteForY]); var currentTileset:TileSet; // only use tiles from this tileset (we get the source image from here) for each( var tileset1:TileSet in tileSets) if (tileGid >= tileset1.firstgid-1 && tileGid // am gasit tilesetul potrivit pentru acest gid! curentTileset = tileset1; pauză; var destY: int = spriteForY * tileWidth; var destinație: int = spriteForX * tileWidth; // matematica de bază pentru a afla de unde vine placile de pe imaginea sursă tileGid - = currentTileset.firstgid -1; var sursaY: int = Math.ceil (tileGid / currentTileset.tileAmountWidth) -1; var sursaX: int = tileGid - (currentTileset.tileAmountWidth * sourceY) - 1; // copiați țigla din tileset pe bitmap-ul nostru dacă (layerMap == 0) screenBitmap.bitmapData.copyPixels (curentTileset.bitmapData, nou dreptunghi (sursăX * currentTileset.tileWidth, sourceY * currentTileset.tileWidth, currentTileset.tileWidth, currentTileset. tileHeight), punct nou (destX, destY), null, null, true); altfel dacă (layerMap == 1) screenBitmapTopLayer.bitmapData.copyPixels (curentTileset.bitmapData, nou dreptunghi (sursăX * currentTileset.tileWidth, sourceY * currentTileset.tileWidth, currentTileset.tileWidth, currentTileset.tileHeight), new Point (destX, destY ), null, null, true);
Aici ajungem în cele din urmă la copierea tilelor în harta noastră.
Inițial începem prin looping prin fiecare coordonată de țiglă pe hartă și pentru fiecare coordonată de țiglă obținem gid
și verificați dacă setul de tiles stocat se potrivește, verificând dacă acesta se află între firstgid
și calculat lastgid
.
Dacă ați înțeles Înțelegerea "gid
" secțiune de sus, această matematică ar trebui să aibă sens. În termenii cei mai de bază, se ia coordonatele țiglelor pe tileset (sourceX
și sourceY
) și copiați-o pe harta noastră la locația în care s-a făcut buclă (destX
și Desty
).
În cele din urmă, la final, numim copyPixel
pentru a copia imaginea plăcii pe stratul de bază sau de bază.
Acum, că copierea straturilor pe hartă este făcută, să analizăm încărcarea obiectelor de coliziune. Acest lucru este foarte puternic deoarece, pe lângă faptul că îl folosim pentru obiecte de coliziune, îl putem folosi și pentru orice alt obiect, cum ar fi o locație de pornire a jocului sau o jucărie, atâta timp cât l-am specificat.
Deci, în partea de jos a addTileBitmapData
funcția, să punem în următorul cod:
pentru fiecare (grupul de obiecte var: XML în xml.objectgroup) var objectGroup: String = objectgroup.attribute ("nume"); switch (objectGroup) caz "Collision": pentru fiecare (var object: XML în objectgroup.object) var dreptunghi: Shape = new Shape (); rectangle.graphics.beginFill (0x0099CC, 1); rectangle.graphics.drawRect (0, 0, object.attribute ("lățime"), object.attribute ("înălțime")); rectangle.graphics.endFill (); rectangle.x = object.attribute ("x"); rectangle.y = object.attribute ("y"); collisionTiles.push (dreptunghi); addChild (dreptunghi); pauză; implicit: urmărire ("tip de obiect nerecunoscut:", objectgroup.attribute ("nume"));
Aceasta va trece prin straturile obiectului și va căuta stratul cu numele "Coliziune
"Când o găsește, este nevoie de fiecare obiect din acel strat, creează un dreptunghi în acea poziție și îl stochează în collisionTiles
matrice. În felul acesta, avem încă o referință la aceasta și ne putem strădui să o verificăm pentru coliziuni dacă avem un jucător.
(În funcție de modul în care sistemul dvs. gestionează coliziunile, poate doriți să faceți ceva diferit.)
În cele din urmă, pentru a afișa harta, vrem să facem mai întâi fundalul și apoi prim-planul, pentru a obține corelarea stratului. În alte limbi, aceasta este pur și simplu o chestiune de redare a imaginii.
// încărcați fundalul layer addChild (screenBitmap); // dreptunghi doar pentru a demonstra cum ar arăta ceva între straturi var playerExample: Shape = new Shape (); playerExample.graphics.beginFill (0x0099CC, 1); playerExample.graphics.lineStyle (2); // conturul dreptunghiului jucatorExample.graphics.drawRect (0, 0, 100, 100); playerExample.graphics.endFill (); playerExample.x = 420; playerExample.y = 260; collisionTiles.push (playerExample); addChild (playerExample); // încărcați stratul superior addChild (screenBitmapTopLayer);
Am adaugat un pic de cod intre straturile de aici doar pentru a demonstra cu un dreptunghi ca stratificare functioneaza intr-adevar. Iată rezultatul final:
Vă mulțumim că ați făcut timp pentru a finaliza tutorialul. Am inclus un zip care conține un proiect FlashDevelop complet, cu tot codul sursă și active.
Dacă sunteți interesat să faceți mai multe lucruri cu Tiled, un lucru pe care nu l-am acoperit a fost proprietăţi. Utilizarea proprietăților este un salt mic de la parsarea denumirilor de straturi și vă permite să setați un număr mare de opțiuni. De exemplu, dacă v-ați dorit un punct inamic al spațiului, puteți specifica tipul de inamic, dimensiunea, culoarea și totul, din interiorul editorului hărții Tiled!
În sfârșit, după cum probabil ați observat, XML nu este cel mai eficient format pentru stocarea datelor TMX. CSV este un mediu plăcut între parsarea ușoară și stocarea mai bună, dar există, de asemenea, base64 (comprimat necomprimat, zlib comprimat și gzip). Dacă sunteți interesat să utilizați aceste formate în loc de XML, verificați pagina wiki-ului Tiling în format TMX.