Deci vrei vreo explozie, foc, gloanțe sau vrăji magice în jocul tău? Sistemele de particule fac mari efecte grafice simple pentru a-ți îmbogăți puțin jocul. Puteți să jucați mai mult, făcând particulele să interacționeze cu lumea voastră, săriți de mediul înconjurător și alți jucători. În acest tutorial vom implementa câteva efecte particulare simple și de aici vom trece la a face ca particulele să sări de pe glob.
De asemenea, vom optimiza lucrurile prin implementarea unei structuri de date numită quadtree. Quadtrees vă permit să verificați coliziunile mult mai repede decât ați putea fără un singur lucru și sunt ușor de implementat și de înțeles.
Notă: Deși acest tutorial este scris folosind HTML5 și JavaScript, ar trebui să puteți utiliza aceleași tehnici și concepte în aproape orice mediu de dezvoltare a jocului.
Pentru a vedea demo-urile din articol, asigurați-vă că citiți acest articol în Chrome, Firefox, IE 9 sau orice alt browser care acceptă HTML5 și Canvas.Un sistem de particule este un mod simplu de a genera efecte cum ar fi focul, fumul și exploziile.
Creați a emițător de particule, și acest lucru lansează mici "particule" pe care le puteți afișa sub formă de pixeli, cutii sau bitmap-uri mici. Ele urmează fizica simplă Newtoniană și schimbă culoarea în timp ce se mișcă, rezultând efecte grafice dinamice, personalizabile.
Sistemul nostru de particule va avea câțiva parametri de acord:
Dacă fiecare particulă a dat naștere exact la fel, am avea doar un flux de particule, nu un efect de particule. Deci, permiteți și variabilitatea configurabilă. Acest lucru ne oferă câțiva parametri suplimentari pentru sistemul nostru:
Încheiem cu o clasă de sisteme de particule care începe astfel:
Parametru Parametru Parametru Parametrii Parametrii Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri Parametri ([și noul Culoare (255, 255, 255, 1), noua Culoare (0, 0) 0, 0, 0)]), // Unghiul în care particula se va declanșa la (și cât de mult poate varia aceasta) unghiul: 0, unghiul Variație: Math.PI * 2, // Viteza de acțiune a particulei se va trage la (0, 30.8), // Un obiect pentru a testa coliziuni împotriva și factorul de amortizare a sarcinii // pentru colizorul de coliziuni: null, bounceDamper: 0.5; // Suprascrieți parametrii impliciți cu parametrii furnizați pentru (var p în paramuri) this.params [p] = params [p]; this.particles = [];
Fiecare cadru trebuie să facă trei lucruri: să creeze particule noi, să mutați particulele existente și să trageți particulele.
Crearea particulelor este destul de simplă. Dacă creăm 300 de particule pe secundă și a trecut 0,05 secunde de la ultimul cadru, noi creăm 15 particule pentru cadru (care în medie se ridică la 300 pe secundă).
Ar trebui să avem o bucla simplă care arată astfel:
var newParticlesThisFrame = this.params.particlesPerSecond * frameTime; pentru (var i = 0; i < newParticlesThisFrame; i++) this.spawnParticle((1.0 + i) / newParticlesThisFrame * frameTime);
Al nostru spawnParticle ()
funcția creează o nouă particulă bazată pe parametrii sistemului nostru:
ParticleSystem.prototype.spawnParticle = funcție (offset) // Vrem să tragem particulele la un unghi aleatoriu și o viteză aleatorie // în parametrii dictați pentru acest sistem var angle = randVariation (this.params.angle, this. params.angleVariation); var viteză = randRange (this.params.minVelocity, this.params.maxVelocity); var life = randVariation (this.params.particleLife, this.params.particleLife * this.params.lifeVariation); // Viteza noastră inițială se va deplasa la viteza pe care am ales-o mai sus în direcția unghiului pe care am ales-o viteza var = nou punct () de la polar (unghi, viteză); // Dacă am creat fiecare particulă la "pos", atunci fiecare particulă // creată într-un cadru ar începe în același loc. // În schimb, acționăm ca și cum am crea particula continuu între // acest cadru și cadrul anterior, pornind-o la un anumit decalaj // de-a lungul căii sale. var pos = this.params.pos.clone () adăugați (velocity.times (offset)); // Construiește un nou obiect de particule din parametrii pe care i-am ales acest parametru.push (Particule noi (acest paragraf, pos, viteză, viață)); ;
Alegem viteza inițială dintr-un unghi și viteză aleatorii. Apoi folosim fromPolar ()
pentru a crea un vector de viteză carteziană din combinația unghi / viteză.
Trigonometria de bază dă fromPolar
metodă:
Point.prototype.fromPolar = funcție (ang, rad) this.x = Math.cos (ang) * rad; aceasta.y = Math.sin (ang) * rad; returnați acest lucru; ;
Dacă aveți nevoie să citiți ușor trigonometria, toată trigonometria pe care o folosim este derivată din Unitatea cercului.
Miscarea particulelor urmeaza legile fundamentale newtoniene. Particulele au toate o viteză și o poziție. Viteza noastră este acționată de forța gravitației, iar poziția noastră se schimbă proporțional cu gravitatea. În cele din urmă, trebuie să ținem evidența vieții fiecărei particule, altfel particulele nu ar muri niciodată, am ajunge la prea multe și sistemul s-ar opri. Toate aceste acțiuni au loc proporțional cu timpul dintre cadre.
Particle.prototype.step = funcția (frameTime) this.velocity.add (this.params.gravity.times (frameTime)); this.pos.add (this.velocity.times (frameTime)); this.life - = frameTime; ;
În cele din urmă trebuie să tragem particulele noastre. Modul în care implementați acest lucru în jocul dvs. va varia foarte mult pe baza platformei și platformei și cât de avansat doriți ca redarea să fie. Acest lucru poate fi la fel de simplu ca plasarea unui singur pixel colorat, mutarea unei perechi de triunghiuri pentru fiecare particulă, trasată de un shader complex de GPU.
În cazul nostru, vom profita de API Canvas pentru a desena un mic dreptunghi pentru particulă.
Particle.prototype.draw = funcția (ctx, frameTime) // Nu este nevoie să desenați particula dacă nu este în viață. dacă (this.isDead ()) returnează; // Vrem sa calatorim prin gradientul de culori, pe masura ce varsta particulei var varPercent = 1.0 - this.life / this.maxLife; var culoarea = this.params.colors.getColor (lifePercent); // Configurați culorile ctx.globalAlpha = color.a; ctx.fillStyle = color.toCanvasColor (); // Completați dreptunghiul la poziția particulei ctx.fillRect (this.pos.x - 1, this.pos.y - 1, 3, 3); ;
Interpolarea culorilor depinde de faptul dacă platforma pe care o utilizați furnizează o clasă de culori (sau un format de reprezentare), indiferent dacă furnizează un interpolator pentru dvs. și cum doriți să abordați întreaga problemă. Am scris o clasă de gradienți mici care permite interpolarea ușoară între mai multe culori și o clasă de culoare mică, care oferă funcționalitatea de a interpola între două culori.
Color.prototype.interpolate = funcție (procent, altul) retur nou culoare (this.r + (other.r - this.r) * procente, this.g + (other.g - this.g) *% .b + (alte.b - this.b) * procente, this.a + (other.a - this.a) * procente); ; Gradient.prototype.getColor = funcție (procente) // Poziția culoare în virgulă mobilă în array var colorF = procente * (this.colors.length - 1); // Rotiți în jos; aceasta este culoarea specificată în array // sub culoarea noastră curentă var color1 = parseInt (colorF); //A rotunji; aceasta este culoarea specificată în matricea // deasupra culorii noastre actuale var color2 = parseInt (colorF + 1); // Interpolate între cele două cele mai apropiate culori (folosind metoda de mai sus) returnează acest. Culori [color1] .interpolate ((colorF - color1) / (color2 - color1), this.colors [color2]); ;
După cum puteți vedea în demo-ul de mai sus, acum avem câteva efecte particulare de bază. Ele nu au nicio interacțiune cu mediul din jurul lor. Pentru ca aceste efecte să facă parte din lumea jocurilor noastre, le vom face să se sară de pe pereții din jurul lor.
Pentru a începe, sistemul de particule va lua acum Collider ca parametru. Va fi sarcina colocviatorului de a spune unei particule dacă s-a prăbușit în orice. Etapa()
metoda unei particule arată acum:
Particle.prototype.step = function (frameTime) // Salveaza ultima pozitie var lastPos = this.pos.clone (); // mutați acest.velocity.add (this.params.gravity.times (frameTime)); this.pos.add (this.velocity.times (frameTime)); // Poate această particulă să sară? if (this.params.collider) // Verificați dacă am lovit ceva var intersect = this.params.collider.getIntersection (new Line (lastPos, this.pos)); if (intersect! = null) // Dacă da, ne reinițializăm poziția și ne actualizăm viteza // pentru a reflecta coliziunea this.pos = lastPos; this.velocity = intersect.seg.reflect (this.velocity) .times (this.params.bounceDamper); this.life - = frameTime; ;
Acum, de fiecare dată când particula se mișcă, îi întrebăm pe ciocan dacă calea sa de mișcare sa "ciocnit" prin intermediul getIntersection ()
metodă. Dacă este așa, ne reinițializăm poziția (astfel încât să nu se afle în interiorul intersecției) și să reflecte viteza.
O implementare de bază "collider" ar putea arăta astfel:
// Creează o colecție de segmente de linie care reprezintă funcția globală de joc Collider (lines) this.lines = lines; // Returnează orice segment de linie intersectat de "path", altfel Collider.prototype.getIntersection = funcția (calea) pentru (var i = 0; i < this.lines.length; i++) var intersection = this.lines[i].getIntersection(path); if (intersection) return intersection; return null; ;
Observați o problemă? Fiecare particulă trebuie să sune collider.getIntersection ()
și apoi fiecare getIntersection
apelul trebuie să verifice împotriva oricărui "zid" din lume. Dacă aveți 300 de particule (un fel de număr scăzut) și 200 de pereți în lumea voastră (nu nerezonabile), efectuați 60 000 de teste de intersecție a liniei! Acest lucru vă poate împiedica jocul, mai ales cu mai multe particule (sau cu lumi mai complexe).
Problema cu colizorul simplu este că verifică fiecare perete pentru fiecare particulă. Dacă particulele noastre se află în cadranul din partea dreaptă superioară a ecranului, nu ar trebui să pierdem timpul verificând dacă s-au prăbușit în pereți care se află doar în partea de jos sau din stânga ecranului. Deci, în mod ideal, vrem să eliminăm orice verificări pentru intersecții în afara cadranului de sus-dreapta:
Acesta este doar un sfert din cecuri! Acum să mergem și mai departe: dacă particula se află în cadranul stânga sus al cvadrantului din dreapta-sus al ecranului, ar trebui să verificăm doar acei pereți din același cvadrant:
Quadtrees vă permit să faceți exact acest lucru! Mai degrabă decât să testeze împotriva toate pereți, vă împărțiți pereții în cadrane și subcadrante pe care le ocupă, deci trebuie doar să verificați câteva cadrane. Puteți trece cu ușurință de la 200 de cecuri pe particulă la doar 5 sau 6.
Pașii pentru a crea un quadtree sunt după cum urmează:
Pentru a construi quadtree noastre luăm un set de "pereți" (segmente de linie) ca parametru, și dacă prea multe sunt cuprinse în dreptunghiul nostru, vom subdiviza în dreptunghiuri mai mici, iar procesul se repetă.
QuadTree.prototype.addSegments = funcția (segs) pentru (var i = 0; i < segs.length; i++) if (this.rect.overlapsWithLine(segs[i])) this.segs.push(segs[i]); if (this.segs.length > 3) this.subdivide (); ; QuadTree.prototype.subdivide = funcția () var w2 = această.recv./ 2, h2 = această.recv.h / 2, x = această.recv.x, y = această.recv.y; this.quads.push (noul QuadTree (x, y, w2, h2)); this.quads.push (noul QuadTree (x + w2, y, w2, h2)); this.quads.push (noul QuadTree (x + w2, y + h2, w2, h2)); this.quads.push (noul QuadTree (x, y + h2, w2, h2)); pentru (var i = 0; i < this.quads.length; i++) this.quads[i].addSegments(this.segs); this.segs = []; ;
Puteți vedea întreaga clasă QuadTree aici:
/ ** * @constructor * / funcția QuadTree (x, y, w, h) this.thresh = 4; this.segs = []; this.quads = []; acest lucru este corect = nou Rect2D (x, y, w, h); QuadTree.prototype.addSegments = funcția (segs) pentru (var i = 0; i < segs.length; i++) if (this.rect.overlapsWithLine(segs[i])) this.segs.push(segs[i]); if (this.segs.length > this.thresh) this.subdivide (); ; QuadTree.prototype.getIntersection = funcția (seg) if (! This.rect.overlapsWithLine (seg)) returnează null; pentru (var i = 0; i < this.segs.length; i++) var s = this.segs[i]; var inter = s.getIntersection(seg); if (inter) var o = ; return s; for (var i = 0; i < this.quads.length; i++) var inter = this.quads[i].getIntersection(seg); if (inter) return inter; return null; ; QuadTree.prototype.subdivide = function() var w2 = this.rect.w / 2, h2 = this.rect.h / 2, x = this.rect.x, y = this.rect.y; this.quads.push(new QuadTree(x, y, w2, h2)); this.quads.push(new QuadTree(x + w2, y, w2, h2)); this.quads.push(new QuadTree(x + w2, y + h2, w2, h2)); this.quads.push(new QuadTree(x, y + h2, w2, h2)); for (var i = 0; i < this.quads.length; i++) this.quads[i].addSegments(this.segs); this.segs = []; ; QuadTree.prototype.display = function(ctx, mx, my, ibOnly) var inBox = this.rect.containsPoint(new Point(mx, my)); ctx.strokeStyle = inBox ? '#FF44CC' : '#000000'; if (inBox || !ibOnly) ctx.strokeRect(this.rect.x, this.rect.y, this.rect.w, this.rect.h); for (var i = 0; i < this.quads.length; i++) this.quads[i].display(ctx, mx, my, ibOnly); if (inBox) ctx.strokeStyle = '#FF0000'; for (var i = 0 ; i < this.segs.length; i++) var s = this.segs[i]; ctx.beginPath(); ctx.moveTo(s.a.x, s.a.y); ctx.lineTo(s.b.x, s.b.y); ctx.stroke(); ;
Testarea pentru intersecția cu un segment de linie este efectuată într-un mod similar. Pentru fiecare dreptunghi facem urmatoarele:
QuadTree.prototype.getIntersection = funcția (seg) if (! This.rect.overlapsWithLine (seg)) returnează null; pentru (var i = 0; i < this.segs.length; i++) var s = this.segs[i]; var inter = s.getIntersection(seg); if (inter) var o = ; return s; for (var i = 0; i < this.quads.length; i++) var inter = this.quads[i].getIntersection(seg); if (inter) return inter; return null; ;
Odată ce trecem a quadtree
obiect pentru sistemul nostru de particule ca "collider", obținem căutări rapide. Consultați demo-ul interactiv de mai jos - utilizați mouse-ul pentru a vedea care segmente de linie vor fi testate de quadtree!
Sistemul de particule și quadtree prezentate în acest articol sunt sisteme de învățământ rudimentar. Unele alte idei pe care ar trebui să le luați în considerare atunci când le implementați: