Folosind măști de țiglă pentru a seta un tip de plăci pe baza dalelor sale înconjurătoare

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.


Ce este o mască pentru placi?

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).


Figura 1. Domeniul local al unei plăci.

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.


Figura 2. Mască pentru identificarea unui bloc de murdărie ca bloc de noroi. Placile gri pot fi orice alte placi.

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).


Figura 3. Cele patru orientări tipice ale unei măști de țiglă: 0 °, 90 °, 180 °, 270 °.

Și, uneori, veți găsi că și aranjamentele mascilor chiar și foarte simple pot fi oglindite și nu pot fi suprapuse (Figura 4).


Figura 4. Un exemplu de chiralitate într-o mască de țiglă. Rândul superior nu poate fi rotit pentru a se potrivi cu rândul de jos.

Structuri de stocare ale unei măști de placi

Stocarea elementelor sale

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.

Depozitarea măștii de țiglă

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).


Figura 5. Stocarea unei măști de dale 2D într-o structură 1D.

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.

Stocarea informațiilor de rotație

Î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).


Codul cerințelor

Odată ce explicațiile de bază au fost explicate, putem preciza ce trebuie să facă codul:

  1. Definiți și păstrați măștile de țiglă.
  2. Rotiți automat măștile de țiglă, în loc să trebuiască să păstrați manual patru exemplare rotite ale aceleiași definiții.
  3. Luați o gamă locală de țiglă.
  4. Evaluați gama locală împotriva unei măști de țiglă.

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).


Definiți, rotiți și păstrați măștile pentru plăci

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 List orice = 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. oriceeste 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.


Luați o gamă locală de țiglă

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; 

Evaluați o zonă locală cu o mască pentru placi

Ș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); 

Evaluare și concluzii

Avantajele acestei implementări

  • Foarte scalabil; trebuie doar să adăugați mai multe definiții pentru măști de țiglă.
  • Verificările efectuate de o mască de țiglă pot fi cât de complexe vă doriți.
  • Codul este destul de transparent și viteza acestuia poate fi îmbunătățită prin efectuarea câtorva verificări suplimentare asupra gamei locale înainte de a începe să treceți prin măști de țiglă.

Dezavantaje ale acestei implementări

  • Poate fi foarte scump. În cel mai rău caz absolut, veți avea (36 × n) + 1 accesează și apelează la 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.

Concluzie

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ă.