Folosită în mod obișnuit în jocurile pe bază de țiglă, măștile de țiglă vă permit să schimbați o țiglă în funcție de vecinii săi, permițându-vă să amestecați terenuri, să înlocuiți plăcile și multe altele. În acest tutorial, vă vom arăta o metodă scalabilă și reutilizabilă pentru a detecta dacă vecinii imediați ai unei plăci se potrivesc cu unul din numeroasele modele pe care le-ați setat.
Notă: Deși acest tutorial este scris folosind C # și Unity, ar trebui să puteți utiliza aceleași tehnici și concepte în aproape orice mediu de dezvoltare a jocului.
Luați în considerare următoarele: faceți un fel de Terraria și doriți ca blocurile de murdărie să se transforme în blocuri de noroi dacă sunt lângă apă. Să presupunem că lumea ta are mult mai multă apă decât murdărie, așa că cel mai ieftin mod de a face asta este verificarea fiecărui bloc de murdărie pentru a vedea dacă este lângă apă și nu invers. Acesta este un moment destul de bun pentru a utiliza o tigla masca.
Măștile de mătase sunt la fel de vechi ca munții și se bazează pe o idee simplă. Într-o rețea 2D de obiecte, o țiglă poate avea alte opt plăci direct adiacente. O vom numi asta zona locală a plăcii centrale (Figura 1).
Pentru a decide ce să faceți cu plăcuța de murdărie, veți compara gama locală cu un set de reguli. De exemplu, puteți privi direct deasupra blocului de murdărie și puteți vedea dacă există apă acolo (Figura 2). Setul de reguli pe care îl utilizați pentru a evalua gama locală este dvs. tigla masca.
Desigur, veți dori să verificați și apa în celelalte direcții, astfel încât cele mai multe măști de tigla au mai mult de un aranjament spațial (Figura 3).
Și, uneori, veți găsi că și aranjamentele mascilor chiar și foarte simple pot fi oglindite și nu pot fi suprapuse (Figura 4).
Fiecare celulă a unei măști de țiglă are un element în interiorul acesteia. Elementul de la acea celulă este comparat cu elementul corespunzător din intervalul local pentru a vedea dacă se potrivesc.
Eu folosesc Liste ca elemente. Listele îmi permit să efectuez comparații de complexitate arbitrară și linia C # oferă linii foarte bune de îmbinare a listelor în liste mai mari, reducând numărul de modificări pe care aș putea uita să le fac.
De exemplu, o listă de plăci de perete poate fi combinată cu o listă de plăci de pardoseală și o listă de plăci de deschidere (uși și ferestre) pentru a produce o listă de plăci structurale sau listele de plăci de mobilier din camere individuale pot fi combinate pentru a face o listă a întregului mobilier. Alte jocuri ar putea avea liste de gresie care pot fi arse sau afectate de gravitate.
Modul intuitiv de a stoca o mască de țiglă este ca o matrice 2D, dar accesarea matricelor 2D poate fi lentă, și o să o faceți foarte mult. Știm că o gamă locală și o mască de țiglă vor consta întotdeauna din nouă elemente, astfel încât să putem evita această pedeapsă de timp utilizând o matrice simplă 1D, citind intervalul local în ea de sus în jos, de la stânga la dreapta (Figura 4).
Relațiile spațiale dintre elemente sunt păstrate dacă vreodată aveți nevoie de ele (nu am nevoie de ele până acum) și arrays pot stoca aproape orice. Tilemaskele în acest format pot fi de asemenea ușor rotate prin citirea / scrierea offset.
În unele cazuri, poate doriți să rotiți țigla centrală în funcție de împrejurimile acesteia, cum ar fi atunci când plasați un perete lângă alte pereți. Îmi place să folosesc matricele jagged pentru a menține cele patru rotații ale unei măști de țiglă în același loc, folosind indexul matricei exterioare pentru a indica rotația curentă (mai mult pe cea din cod).
Odată ce explicațiile de bază au fost explicate, putem preciza ce trebuie să facă codul:
Următorul cod este scris în C # pentru unitate, dar conceptele trebuie să fie destul de portabile. Exemplul este unul din propria mea lucrare în conversia procedurală a hărților de tip roguelike doar în 3D (plasarea unei secțiuni drepte a peretelui, în acest caz).
Automatizăm toate acestea cu o singură metodă de apel la a DefineTilemask
metodă. Iată un exemplu de utilizare, cu declarația de mai jos.
public static readonly Listorice = listă nouă () ; public static readonly List ignorată = Listă nouă () ", '_', public static readonly List wall = new List () '#', 'D', 'W'; lista publică statică [] [] outerWallStraight = MapHelper.DefineTilemask (orice, ignorat, orice, perete, orice, perete, orice, orice);
Definesc masca de tigla în forma sa neatintită. A sunat lista ignorate
stochează caractere care nu descriu nimic din programul meu: spații care sunt ignorate; și sublinieri, pe care le folosesc pentru a arăta un indice de matrice nevalid. O placă la (0,0) (colțul din stânga sus) al unei matrice 2D nu va avea nimic la nord sau la vest, de exemplu, astfel încât gama locală devine subliniată acolo. orice
este o listă goală care este întotdeauna evaluată ca o potrivire pozitivă.
lista publică statică[] [] Definițimailamaila (lista nW, Listă n, Listă nE, Listă w, Listă centru, Listă e, Lista sW, Listă s, listă sE) Lista [] șablon = listă nouă [9] nW, n, nE, w, centru, e, sW, s, sE; întoarce noua listă [4] [] RotateLocalRange (șablon, 0), RotateLocalRange (șablon, 1), RotateLocalRange (șablon, 2), RotateLocalRange (șablon, 3); public Static List [] RotateLocalRange (Listă [] localRange, rotații int) Listă [] rotatedList = listă nouă [9] localRange [0], localRange [1], localRange [2], localRange [3], localRange [4], localRange [5], localRange [6], localRange [7], localRange [8]; pentru (int i = 0; i < rotations; i++) List [] tempList = Listă nouă [9] rotatedList [6], rotitList [3], rotitList [0], rotitList [7], rotitList [4], rotitList [1], rotatedList [8], rotatedList [5], rotatedList [2]; rotatedList = tempList; retur rotatedList;
Merită explicat implementarea acestui cod. În DefineTilemask
, Vă ofer nouă liste ca argumente. Aceste liste sunt plasate într-o matrice temporară 1D și apoi se rotesc în pași de + 90 ° scriind pe o nouă matrice într-o ordine diferită. Dalemaskele rotate sunt apoi stocate într-o matrice jaggedă, a cărei structură o folosesc pentru a transmite informații rotaționale. Dacă se potrivește matricea țiglă la indexul exterior 0, atunci țigla este plasată fără rotire. Un meci la indexul exterior 1 dă țiglă o rotație + 90 °, și așa mai departe.
Aceasta este simplă. Citește gama locală a plăcii curente într-o matrice de caractere 1D, înlocuind orice indici invalizi cu subliniere.
/ * Utilizare: char [] localRange = GetLocalRange (plan, rând, coloană); planul este matricea 2D care definește clădirea. rândul și coloana sunt indicii de matrice ale plăcii curente care este evaluat. * / public static char [] GetLocalRange (char [,] thisArray, int rând, int coloană) char [] localRange = new char [9]; int localRangeCounter = 0; // Iteratoarele încep să numere de la -1 pentru a compensa citirea în sus și spre stânga, plasând indicele solicitat în centru. pentru (int i = -1; i < 2; i++) for (int j = -1; j < 2; j++) int tempRow = row + i; int tempColumn = column + j; if (IsIndexValid (thisArray, tempRow, tempColumn) == true) localRange[localRangeCounter] = thisArray[tempRow, tempColumn]; else localRange[localRangeCounter] = '_'; localRangeCounter++; return localRange; public static bool IsIndexValid (char[,] thisArray, int row, int column) // Must check the number of rows at this point, or else an OutOfRange exception gets thrown when checking number of columns. if (row < thisArray.GetLowerBound (0) || row > (thisArray.GetUpperBound (0))) return false; dacă (coloana < thisArray.GetLowerBound (1) || column > (thisArray.GetUpperBound (1))) return false; altceva returnează adevărat;
Și aici se întâmplă magia! TrySpawningTile
este dată gama locală, o mască de țiglă, piesa de perete pentru a da naștere în cazul în care se potrivește masca tigla și rândul și coloana plăcii care este evaluat.
Este important faptul că metoda care efectuează comparația reală dintre masca locală și cea de tigla (TileMatchesTemplate
) scapă o rotație a măștii țiglelor imediat ce constată o nepotrivire. Nu este prezentată o logică de bază care să definească ce măști de tigla trebuie să folosească pentru tipurile de plăci (nu ați folosi o mască pentru plăci de perete pe o bucată de mobilier, de exemplu).
/ * Utilizare: TrySpawningTile (localRange, TileIDs.outerWallStraight, outerWallWall, floorEdgingHalf, rând, coloană); * / / Aceste quaternioane au o rotație de -90 de-a lungul X, deoarece modelele trebuie să fie rotite în unitate datorită axei diferite în Blender. statică publică readonly Quaternion ROTATE_NONE = Quaternion.Euler (-90, 0, 0); statică publică readonly Quaternion ROTATE_RIGHT = Quaternion.Euler (-90, 90, 0); public static readonly Quaternion ROTATE_FLIP = Quaternion.Euler (-90, 180, 0); statică publică readonly Quaternion ROTATE_LEFT = Quaternion.Euler (-90, -90, 0); bool TrySpawningTile (char [] armaArray, Listă[] [] șablonArray, GameObject tilePrefab, int rând, coloană int) Quaternion horizontalRotation; dacă (TileMatchesTemplate (needleArray, templateArray, out horizontalRotation) == true) SpawnTile (tilePrefab, rând, coloană, orizontalăRotație); return true; altfel return false; statice publice bool TileMatchesTemplate (char [] needleArray, List [] [] tileMaskJaggedArray, afară Quaternion horizontalRotation) horizontalRotation = ROTATE_NONE; pentru (int i = 0; i < (tileMaskJaggedArray.Length); i++) for (int j = 0; j < 9; j++) if (j == 4) continue; // Skip checking the centre position (no need to ascertain that a block is what it says it is). if (tileMaskJaggedArray[i][j].Count != 0) if (tileMaskJaggedArray[i][j].Contains (needleArray[j]) == false) break; if (j == 8) // The loop has iterated nine times without stopping, so all tiles must match. switch (i) case 0: horizontalRotation = ROTATE_NONE; break; case 1: horizontalRotation = ROTATE_RIGHT; break; case 2: horizontalRotation = ROTATE_FLIP; break; case 3: horizontalRotation = ROTATE_LEFT; break; return true; return false; void SpawnTile (GameObject tilePrefab, int row, int column, Quaternion horizontalRotation) Instantiate (tilePrefab, new Vector3 (column, 0, -row), horizontalRotation);
List.Contains ()
înainte de a găsi un meci, unde n este numărul de definiții ale măștilor de țiglă pe care le căutați. Este esențial să faceți ceea ce puteți pentru a restrânge lista măștilor de țiglă înainte de a începe căutarea.Măștile de mătase au utilizări nu numai în generația mondială sau estetică, ci și în elemente care afectează modul de joc. Nu este greu de imaginat un joc de puzzle în cazul în care măștile de țiglă ar putea fi folosite pentru a determina starea tabloului sau posibilele mișcări ale pieselor, sau uneltele de editare ar putea utiliza un sistem similar pentru a bloca blocuri una de cealaltă. Acest articol a demonstrat o implementare de bază a ideii și sper că ați găsit-o utilă.