Codificare Terrain Pixel Destructibil Cum sa faci totul sa explodeze

În acest tutorial, vom implementa un teren pixel complet distructiv, în stilul jocurilor precum Cortex Command și Worms. Veți învăța cum să faceți ca lumea să explodeze oriunde îl trageți - și cum să faceți "praful" să se așeze pe teren pentru a crea noi terenuri.

Notă: Deși acest tutorial este scris în Procesarea și compilat cu Java, ar trebui să puteți utiliza aceleași tehnici și concepte în aproape orice mediu de dezvoltare a jocului.


Rezultatul final al rezultatelor

Puteți juca demo-ul, de asemenea. WASD pentru a muta, faceți clic pe stânga pentru a trage gloanțe explozive, faceți clic dreapta pentru a pulveriza pixeli.


Pasul 1: Terrain

În nisipul nostru de joc sidescrolling, terenul va fi mecanicul principal al jocului nostru. Algoritmi similari au de multe ori o imagine pentru textura terenului, iar alta ca o masca alb-negru pentru a defini care pixeli sunt solizi. În acest demo, terenul și textura acestuia sunt toate o singură imagine, iar pixelii sunt solizi pe baza faptului că sunt transparenți sau nu. Abordarea mascată ar fi mai potrivită dacă doriți să definiți proprietățile fiecărui pixel, cum ar fi probabilitatea că va dispărea sau cât de bouncy va fi pixelul.

Pentru a face terenul, cutia de nisip deseneaza mai intai pixelii statici, apoi pixelii dinamici cu totul altceva deasupra.

Terrainul are, de asemenea, metode pentru a afla dacă un pixel static la o locație este solid sau nu și metode pentru eliminarea și adăugarea de pixeli. Probabil cel mai eficient mod de a stoca imaginea este ca o matrice dimensională. Obținerea unui indice 1D dintr-o coordonată 2D este destul de simplă:

index = x + y * lățime

Pentru ca pixelii dinamici să sări, trebuie să fim capabili să aflăm suprafața normală în orice moment. Faceți buclă printr-o zonă pătrată în jurul punctului dorit, găsiți toți pixelii solidi din apropiere și media poziției lor. Luați un vector din acea poziție în punctul dorit, inversați-l și normalizați-l. E normalitatea ta!

Liniile negre reprezintă normalele la teren în diferite puncte.

Iată cum arată acest lucru în cod:

 (x + w, y + y) este un număr arbitrar pentru y = -3 până la 3 // numărul utilizatorilor mai mare pentru suprafețe mai netede dacă pixelul este solid la (x + w, y + h) avg - = (x, y) lungime = sqrt (avgX * avgX + avgY * avgY) // distanța de la avg la centrul de întoarcere medie / lungime // normalizați vectorul împărțind distanța respectivă

Pasul 2: Pixelul dinamic și fizica

Însuși "Terrain" stochează toți pixelii statici care nu se mișcă. Pixelii dinamici sunt pixeli în mișcare și sunt stocați separat de pixelii statici. Pe măsură ce terenul explodează și se reglează, pixelii se schimbă între stări statice și dinamice, în timp ce dislocă și se ciocnesc. Fiecare pixel este definit de un număr de proprietăți:

  • Poziția și viteza (necesare pentru ca fizica să funcționeze).
  • Nu doar locația, ci și locația anterioară a pixelilor. (Putem scana între cele două puncte pentru a detecta coliziunile.)
  • Alte proprietăți includ culoarea pixelului, lipicioasa și bounciness.

Pentru ca pixelul să se miște, poziția sa trebuie să fie transmisă cu viteza sa. Integrarea Euler, în timp ce este inexactă pentru simulări complexe, este suficient de simplă pentru a ne mișca eficient particulele:

poziția = poziția + viteza * timpul scurs

timpul scurs este perioada de timp scursă de la ultima actualizare. Acuratețea oricărei simulări poate fi complet întreruptă dacă timpul scurs este prea variabilă sau prea mare. Nu este vorba de o problemă pentru pixelii dinamici, dar va fi și pentru alte sisteme de detectare a coliziunilor.

Vom folosi timestepsuri de dimensiune fixă, luând timpul scurs și împărțind-o în bucăți de dimensiuni constante. Fiecare bucată este o "actualizare" completă a fizicii, cu toate cele rămase fiind trimise în cadrul următor.

 elapsedTime = lastTime - curentTime lastTime = currentTime // reinițializează lastTime // adaugă timpul care nu a putut fi utilizat ultimul cadru elapsedTime + = leftOverTime // diviza-l în bucăți de 16 ms timesteps = floor (elapsedTime / 16) // timpul de stocare nu am putut folosi pentru următorul cadru. leftOverTime = elapsedTime - timesteps pentru (i = 0; i < timesteps; i++)  update(16/1000) // update physics 

Pasul 3: Detectarea coliziunilor

Detectarea coliziunilor pentru pixelii care zboară este la fel de simplă ca și trasarea unor linii.

Algoritmul de linie al lui Bresenham a fost dezvoltat în 1962 de un domn numit Jack E. Bresenham. Până în prezent, a fost folosit pentru a desena linii simple aliate în mod eficient. Algoritmul se lipsește strict pe întregi și folosește în majoritate adunări și scăderi pentru a realiza linii de complot efiectant. Astăzi o vom folosi pentru un alt scop: detectarea coliziunilor.

Folosesc cod împrumutat de la un articol pe gamedev.net. În timp ce majoritatea implementărilor algoritmului de linie al lui Bresenham rearanjează ordinea desenului, acest lucru ne permite să scanăm întotdeauna de la început până la sfârșit. Ordinea este importantă pentru detectarea coliziunilor, altfel vom detecta coliziunile la capătul nepotrivit al traseului pixelilor.

Panta este o parte esențială a algoritmului de linie al lui Bresenham. Algoritmul funcționează prin împărțirea pantei în componentele sale de "creștere" și "rulare". Dacă, de exemplu, panta liniei a fost de 1/2, putem complotul prin plasarea a două puncte orizontal, urcând (și dreapta) unul și apoi două.

Algoritmul pe care îl prezint aici contabilizează toate scenariile, indiferent dacă liniile au o pantă pozitivă sau negativă sau dacă sunt verticale. Autorul explică cum îl derivă pe gamedev.net.

(INT), int int, int int, int int lastX, int lastY) int int deltax = (int) abs (lastX - startX) int deltay = (int) int) startY int xinc1, xinc2, yinc1, yinc2 // Determina daca x si y creste sau scad daca (lastX> = startX) // Valorile x cresc xinc1 = 1 xinc2 = 1 else // The Valorile x sunt în scădere xinc1 = -1 xinc2 = -1 dacă (lastY> = startY) // Valorile y cresc yinc1 = 1 yinc2 = 1 altceva // Valorile y sunt în scădere yinc1 = - 1 (y) = 1, int, num, numadd, numpixels dacă (deltax> = deltay) // Există cel puțin o valoare x pentru fiecare valoare y xinc1 = > = numitor yinc2 = 0 // Nu modificați y pentru fiecare iterație den = deltax num = deltax / 2 numadd = deltay numpixels = deltax // Există mai multe valori x decât valorile y altceva // Nu există cel puțin o valoare y pentru fiecare valoare x xinc2 = 0 // Nu modificați x pentru fiecare iterație yinc1 = 0 // Nu ange y când numitorul = numitorul den = delta num = deltay / 2 numadd = deltax numpixels = deltay // Există mai multe valori y decât valorile x int prevX = (int) startX int prevY = (int) (int curpixel = 0; curpixel <= numpixels; curpixel++)  if (terrain.isPixelSolid(x, y)) return (prevX, prevY) and (x, y) prevX = x prevY = y num += numadd // Increase the numerator by the top of the fraction if (num >= den) // Verificați dacă numărătorul> = numitorul num - = den // Calculați noua valoare a numărătorului x + = xinc1 // Schimbați x după caz ​​y + = yinc1 // Schimbați y după caz x + = xinc2 // Schimbați x după cum este adecvat y + = yinc2 // Schimbați y după caz return null // nimic nu a fost găsit

Pasul 4: Manipularea coliziunilor

Pixelul dinamic poate face unul din cele două lucruri în timpul unei coliziuni.

  • Dacă se mișcă destul de încet, pixelul dinamic este eliminat și unul static este adăugat la terenul în care s-a ciocnit. Lipirea ar fi cea mai simplă soluție. În algoritmul de linie al lui Bresenham, este mai bine să urmăriți un punct anterior și un punct curent. Atunci când se detectează o coliziune, "punctul curent" va fi primul pixel solid pe care îl lovește raza, în timp ce "punctul anterior" este spațiul gol imediat înainte de acesta. Punctul anterior este exact locul unde trebuie să lipim pixelul.
  • Dacă se mișcă prea repede, atunci o sărim de pe teren. Aici intră algoritmul nostru normal de suprafață! Reflectați viteza inițială a mingii peste normal, pentru a o sări.
  • Unghiul fiecărei părți a normalului este același.

 // Viteza proiectului pe normală, înmulțirea cu 2 și scăderea lui de la viteza normală = getNormal (collision.x, collision.y) // viteza proiectului pe normal folosind proiecția produsului dot = velocity.x * normal.x + viteza .y * normal.y // viteza - = normal * proiecție * 2

Pasul 5: Gloanțe și explozii!

Marcatorii funcționează exact ca și pixelii dinamici. Propunerea este integrată în același mod, iar detecția coliziunilor utilizează același algoritm. Singura noastră diferență este manipularea coliziunilor

După ce se detectează o coliziune, gloanțele explodează prin eliminarea tuturor pixelilor statici într-o rază și apoi punând pixeli dinamici în locul lor cu vitezele lor îndreptate spre exterior. Utilizez o funcție pentru a scana o zonă pătrată în jurul unei raze de explozie pentru a afla ce pixeli să dislocă. Ulterior, distanța pixelului de la centru este utilizată pentru a stabili o viteză.

 exploda (x, y, raza) pentru (xPos = x - raza; xPos <= x + radius; xPos++)  for (yPos = y - radius; yPos <= y + radius; yPos++)  if (sq(xPos - x) + sq(yPos - y) < radius * radius)  if (pixel is solid)  remove static pixel add dynamic pixel     

Pasul 6: Jucătorul

Jucătorul nu este o parte centrală a mecanicului de teren distructiv, dar implică o detectare a coliziunii care va fi cu siguranță relevantă pentru problemele care vor veni în viitor. Voi explica cum se detectează și se manipulează coliziunea în demo pentru jucător.

  1. Pentru fiecare margine, bucla de la un colț la altul, verificând fiecare pixel.
  2. Dacă pixelul este solid, începeți din centrul player-ului și scanați spre acel pixel în care atingeți un pixel solid.
  3. Deplasați player-ul departe de primul pixel solid pe care l-ați lovit.

Pasul 7: Optimizarea

Mii de pixeli sunt manipulați dintr-o dată, rezultând într-un pic de presiune asupra motorului fizicii. Ca orice altceva, pentru a face asta repede, aș recomanda să folosiți o limbă care este rezonabil de rapidă. Demo-ul este compilat în Java.

Puteți face lucruri pentru a optimiza și nivelul algoritmului. De exemplu, numărul de particule din explozii poate fi redus prin scăderea rezoluției de distrugere. În mod normal, găsim fiecare pixel și îl transformăm într-un pixel dinamic de 1x1. În schimb, scanați fiecare 2x2 pixeli sau 3x3 și lansați un pixel dinamic de acea dimensiune. În demo vom folosi 2x2 pixeli.

Dacă utilizați Java, colectarea de gunoi va fi o problemă. JVM va găsi periodic obiecte în memorie care nu mai sunt folosite, cum ar fi pixelii dinamici care sunt eliminați în schimbul pixelilor statici și încercați să scăpați de aceia pentru a face loc mai multor obiecte. Ștergerea obiectelor, a obiectelor, necesită timp și de fiecare dată când JVM face o curățare, jocul nostru va îngheța pentru scurt timp.

O posibilă soluție este folosirea unui cache de un fel. În loc să creați / distrugeți obiecte tot timpul, puteți pur și simplu să țineți obiecte moarte (cum ar fi pixeli dinamici) pentru a fi reutilizate ulterior.

Utilizați primitive ori de câte ori este posibil. De exemplu, utilizarea obiectelor pentru poziții și viteze va face lucrurile mai greu pentru colecția de gunoi. Ar fi chiar mai bine dacă ați putea stoca totul ca primitivi în mănunchiuri unidimensionale.


Pasul 8: Faceți-vă propriul

Există multe direcții diferite pe care le puteți lua cu acest mecanic de joc. Caracteristicile pot fi adăugate și personalizate pentru a se potrivi cu orice stil de joc pe care doriți.

De exemplu, coliziunile dintre pixeli dinamici și statici pot fi manipulate diferit. O mască de coliziune sub teren poate fi utilizată pentru a defini lipiciunea fiecărui static pixel, oribilitatea și puterea, sau probabilitatea de a fi dislocat de o explozie.

Există o varietate de lucruri diferite pe care le puteți face și la arme. Gloanțele pot primi o "adâncime de penetrare", pentru a le permite să se miște prin atât de mulți pixeli înainte de a exploda. Mecanica tradițională a pistolului poate fi aplicată, de asemenea, ca o rată variată de foc sau, ca o pușcă, mai multe gloanțe pot fi trase simultan. Puteți chiar, ca și pentru particulele bouncy, să aveți gloanțe săriți de pixeli de metal.


Concluzie

Distrugerea terenului 2D nu este complet unică. De exemplu, viermii clasici și rezervoarele elimină părți ale terenului pe explozii. Comandamentul Cortex utilizează particule asemănătoare similare pe care le folosim aici. Și alte jocuri ar putea fi, dar nu am auzit încă de ei. Aștept cu nerăbdare să văd ce vor face ceilalți dezvoltatori cu acest mecanic.

Cele mai multe dintre ceea ce am explicat aici sunt pe deplin implementate în demo. Vă rugăm să aruncați o privire la sursa ei dacă ceva pare ambiguu sau confuz. Am adăugat comentarii sursei pentru a face cât mai clară posibil. Vă mulțumim pentru lectură!