Creați o scenă de noapte confortabilă, cu zăpadă, utilizând efectele particulelor

Efectele particulelor sunt foarte frecvente în jocuri - e greu să găsești un joc modern care să fie nu utilizati-le. În acest tutorial vom examina cum să construim un motor cu particule destul de complex și să îl folosim pentru a crea o scenă distractivă de zăpadă. Puneți-vă pălăria lână și începeți.

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


Rezultatul final al rezultatelor

Faceți clic și trageți mouse-ul pentru a interacționa cu efectul de zăpadă.

Fără Flash? Consultați acest videoclip al demo-ului pe YouTube:


Înființat

Implementarea demo-ului de mai sus utilizează AS3 și Flash, cu Starling Framework pentru redare accelerată a GPU. O imagine pre-redată a scenei 3D (disponibilă în descărcarea sursei) va fi utilizată ca fundal. În straturile de mijloc vom plasa efecte particulare, iar întreaga scenă va fi înmulțită cu o textură reprezentând atribuirea luminii.


Efectele particulelor

În jocuri trebuie adesea să simulam diferite fenomene vizuale și de mișcare și, adesea, trebuie să afișăm efecte vizuale dinamice, cum ar fi focul, fumul și ploaia. Există mai multe modalități prin care putem face acest lucru; o metodă comună este de a folosi efectele particulelor.

Aceasta implică folosirea multor elemente mici - de obicei, imagini orientate spre camera foto - dar ar putea fi realizate și folosind modele 3D. Obiectivul principal este de a actualiza poziția, scala, culoarea și alte proprietăți ale unui grup de multe particule (probabil câteva mii). Acest lucru va crea o iluzie a modului în care efectul arată în viața reală.


Efectele moderne ale particulelor: particulele GPU din noul motor Unreal 4

În acest tutorial vom simula două efecte: un efect de zăpadă care va consta din multe sprite de fulgi, afectate de vânt și gravitate, și un efect de ceață subtil care va folosi o mână de sprite mari de fum.


Implementarea emițătorilor, colizoarelor și surselor de forță

Efectele tipice ale particulelor constau în unul sau mai mulți emițători de particule. Un emițător este locul unde provin particulele; poate avea diferite forme și au comportamente diferite. Aceasta adaugă poziția inițială și unghiul particulelor și poate, de asemenea, să definească și alți parametri de pornire, cum ar fi viteza inițială.

Vom crea un tip de emițător, a box emitor, care va genera particule în interiorul - ați ghicit-o - o cutie pe care o definim.

Pentru a ne ușura adăugarea mai multor emițători, vom folosi o construcție de programare numită an interfață care definește faptul că clasa care o implementează trebuie să aibă un set definit de metode. În cazul nostru, vom solicita o singură metodă:

 funcția generatePosition (): Punct

Implementarea acestui lucru în emițătorul cutiei este foarte simplă; luăm un punct aleatoriu între punctele minime și maxime care definesc caseta:

 funcția publică generatePosition (): Punctul var randomX: Number = Math.random () * (maxPoint.x - minPoint.x) + minPoint.x; var randomY: Număr = Math.random () * (maxPoint.y - minPoint.y) + minPoint.y; returnați un nou punct (randomX, randomY); 

Pentru a face acest exemplu mai interesant și un pic mai avansat, vom adăuga conceptul de: a Collider și surse de forță.

Un collider va fi un obiect alcătuit din una sau mai multe definiții de geometrie, care pot fi atașate la un emițător de particule. Când o particulă are o interacțiune cu colizorul (adică atunci când intră în geometrie), vom obține un eveniment pentru a decide ce ne-ar plăcea să facem. Aceasta va fi utilizată pentru a opri deplasarea fulgilor de zăpadă atunci când se ciocnesc cu solul.

În același mod ca și cu emițătorii vom folosi o interfață care ne cere să implementăm următoarea funcție:

 funcția se ciocnește (x: Număr, y: Număr): Boolean;

Notă: aceasta este o implementare simplă, deci luăm în considerare doar poziția când verificăm coliziunea.

Punerea în aplicare a colizoarei cutiei este simplă; verificăm dacă punctul se află în limitele casetei:

 funcția publică se ciocnește (x: Număr, y: Număr): Boolean var xInBounds: Boolean = this.minPoint.x < x && this.maxPoint.x > X; var yInBounds: Boolean = this.minPoint.y < y && this.maxPoint.y > y; retur xInBounds && yInBounds; 

Celălalt tip de obiect pe care îl vom introduce este o sursă de forță. Aceasta va avea un efect asupra vitezei particulelor pe baza parametrilor și a poziției și masei particulelor.

Cea mai simplă sursă va fi numită direcțional forța de forță și o vom defini cu un singur vector D (folosit pentru direcția forței și forța). Nu ia în considerare pozițiile particulelor; se aplică forța asupra tuturor particulelor din acest efect. Cu aceasta vom putea simula gravitatea și vântul - pentru vânt direcția va varia în timp, pentru a se simți mai realistă.

Un alt tip de sursă de forță va depinde de distanța dintre un punct definit și particule, devenind mai slab mai departe de centru. Această sursă va fi definită prin poziția sa P și factorul de rezistență S. Vom folosi acest lucru pentru a permite interacțiunea mouse-ului cu zăpada.


Tipurile de surse de forță pe care le vom crea

Sursele de forță vor avea interfața proprie, necesitând implementarea următoarei metode:

 funcția forceInPoint (x: număr, y: număr): punct;

Totuși, de data aceasta avem mai multe implementări: una pentru o sursă de forță de direcție și una pentru o sursă de forță punctuală.

Implementarea sursei de direcție este cea mai simplă dintre cele două:

 funcția publică funcțiaInPoint (x: Număr, y: Număr): Punct /// Fiecare particulă primește aceeași forță înapoi punct nou (forceVectorX, forceVectorY); 

Aceasta este punerea în aplicare a sursei punctiforme:

 /// X. y sunt poziția funcției publice a particulelor forceInPoint (x: Număr, y: Număr): Punctul /// Direcția și distanța var diferențaX: Numărul = x - pozițiaX; var diferențaY: Numărul = y - pozițiaY; var distanta: Numar = Math.sqrt (diferentaX * diferentaX + diferentaY * diferentaY); /// Valoarea Falloff care va reduce puterea forței var falloff: Number = 1.0 / (1.0 + distance); /// Noi normalizăm direcția și folosim falloff și puterea pentru a calcula forța finală var forceX: Number = differenceX / distance * falloff * strength; var forceY: Număr = diferențăY / distanță * falloff * strength; returnează un punct nou (forceX, forceY); 

Rețineți că această metodă va fi apelată în mod continuu. Acest lucru ne permite să modificăm parametrii de forță atunci când efectul este activ. Vom folosi această caracteristică pentru a simula vântul și a adăuga interactivitatea mouse-ului.


Efectul și punerea în aplicare a particulelor

Partea principală a implementării se află în Efect clasa, care este responsabilă pentru reproducerea și actualizarea particulelor.

Cantitatea de particule care se va produce va fi determinată de spawnPerSecond valoare în Actualizați metodă:

 _spawnCounter - = timp; /// Folosind o buclă pentru a înmulți mai multe particule într-un cadru în timp ce (_spawnCounter <= 0)  /// Spawn the number of particles according to the passed time _spawnCounter += (1 / _spawPerSecond) * time; spawnParticle(); 

Actualizarea este un pic mai complexă. Mai întâi, implementarea actualizează forțele, apoi solicită actualizarea simulării particulelor și verifică coliziunile. De asemenea, este responsabil pentru îndepărtarea particulelor atunci când acestea nu mai sunt necesare.

 var i: int = 0; /// folosind în buclă astfel încât să putem scoate particulele din container în timp ce (i < _particles.length)  var particle:Particle = _particles[i]; /// Calculate particle accleration from all forces particle.acceleration = calculateParticleAcceleration(particle); /// Simulate particle particle.update(time); /// Go through the colliders and report collisions if (_colliders && _collisionResponse != null)  for each (var collider:ICollider in _colliders)  if (collider.collides(particle.x, particle.y))  _collisionResponse(particle, collider);    /// remove particle if it's dead if (particle.isDead)  _particles.splice(i, 1); addParticleToThePool(particle); particle.removeFromParent();  else  /// We are in the while loop and need to increment the counter i++;  

Nu am menționat încă cea mai importantă parte a implementării: cum reprezentăm particulele. particulă clasa va moșteni de la un obiect pe care îl putem afișa (imagine) și vom avea câteva proprietăți care vor influența modificarea acestuia în timpul actualizării:

  • startingLife - cât timp o particulă poate rămâne în viață.
  • mobil - dacă poziția particulei va fi schimbată (folosită pentru a îngheța particula în loc).
  • viteză - cât de mult se va mișca particula într-o anumită perioadă de timp.
  • accelerare - cât de mult viteza particulei se va schimba într-o anumită perioadă de timp.
  • viteză unghiulară - cât de repede se schimbă rotația într-o anumită perioadă de timp.
  • fadeInOut - indiferent dacă folosim o valoare alfa fading pentru a crea și distruge cu ușurință particula.
  • alphaModifier - determină valoarea de bază alfa.
  • masa - masa fizică a particulei (utilizată la calcularea accelerației din forțe).

Fiecare particulă are un Actualizați funcție care se numește cu delta de timp (dt). Aș dori să arăt că o parte a acestei funcții se ocupă de actualizarea poziției particulelor, care este obișnuită în jocuri:

 /// actualizează poziția cu viteza x + = _velocity.x * dt; y + = _velocity.y * dt; /// viteza de actualizare cu accelerația _velocity.x + = _acceleration.x * dt; _velocity.y + = _acceleration.y * dt;

Acest lucru se face folosind integrarea Euler și are erori de precizie, dar din moment ce îl folosim doar pentru efecte vizuale, acestea nu ne vor deranja. Dacă faci simulări fizice importante pentru joc, ar trebui să te uiți în alte metode.


Exemplu Efecte

În cele din urmă am ajuns la punctul în care îți voi explica cum să implementezi efectul real. Pentru a face un nou efect, vom extinde Efect clasă.


Texturile particulelor

Lasa sa ninga

Vom începe cu efectul de zăpadă. Mai întâi, așezați un emițător de cutie pe partea superioară a ecranului și folosiți-l pentru a crea câteva particule. Un collider va fi folosit pentru a detecta dacă o particulă a atins podeaua, caz în care o vom seta mobil proprietate la fals.

Cel mai important lucru pe care trebuie să-l asigurăm este că particulele sunt suficient de aleatoare, astfel încât să nu creeze pe ecran ecranele vizibile, care dăunează iluziei. Facem acest lucru în câteva moduri:

  • Viteză de pornire aleatorie - fiecare particulă se va deplasa într-un mod ușor diferit.
  • Scală aleatorie - în afară de dimensiuni diferite, acest lucru adaugă și mai multă profunzime efectului, făcându-l să arate mai mult 3D.
  • Rotație aleatorie - face ca fiecare particulă să pară unică chiar dacă utilizează aceeași imagine.

Inițializăm fiecare particulă de zăpadă în felul următor:

 particle.fadeInOut = adevărat; /// Viața [3, 4> secunde particle.startingLife = 3 + Math.random (); /// Cantitatea mică de viteză de pornire particle.velocity = Point.polar (30, Math.random () * Math.PI * 2.0); /// rotație aleatorie [0, 360> grade particle.rotation = Math.PI * 2.0 * Math.random (); /// Scala aleatorie [0.5, 1> particle.scaleX = particle.scaleY = Math.random () * 0.5 + 0.5;

Pentru a le da o mișcare realistă de a cădea de pe cer, vom folosi o sursă de forță direcțională ca gravitate. Ar fi prea ușor să ne oprim aici, așa că vom adăuga o altă forță direcțională pentru a simula vântul, care va varia în timp.

 /// -20 este un număr arbitrar care a funcționat bine la testarea /// (9.81m / s / s este accelerația reală datorată gravitației pe Pământ) var gravitate: DirectionalField = new DirectionalField (0, -9.81 * -20); /// Inițializarea nu este importantă; valorile se vor schimba în timp _wind = new DirectionalField (1, 0); /// seta forțele la efectul this.forces = new [gravitate, vânt];

Vom varia valoarea vântului utilizând o funcție sinusoidală; aceasta a fost cea mai mare parte determinată prin experimentare. Pentru axa x, ridicăm sinusoidele la puterea de 4, făcându-și vârful mai ascuțit. La fiecare șase secunde va fi un vârf, producând efectul unei puternice rafale de vânt. Pe axa y, vântul va oscila rapid între -20 și 20.

 /// Calculați forța vântului _counter + = time; _wind.forceVectorX = Math.pow (Math.sin (_counter) * 0.5 + 0.5, 4) * 150; _wind.forceVectorY = Math.sin (_counter * 100) * 20;

Aruncați o privire la complotul funcției pentru a înțelege mai bine ce se întâmplă.


Axa x reprezintă timpul; axa y reprezintă viteza vântului. (Nu la scară.)

Adăugați Some Fog

Pentru a finaliza efectul, vom adăuga un efect de ceață subtil, folosind un emițător cutie care acoperă întreaga scenă.

Din moment ce textura pe care o vom folosi pentru particula este relativ mare, emițătorul va fi setat să crească un număr mic de particule. Nivelul alfa al particulei va fi de la început scăzut pentru a împiedica acoperirea completă a scenei. De asemenea, le vom seta să se rotească încet, pentru a simula un efect de vânt.

 /// Acoperă o porțiune mare a ecranului this.emitter = o nouă casetă (0, 40, 640, 400); /// Vrem doar câteva dintre particulele pe ecran la un moment dat this.spawnPerSecond = 0.05; this.setupParticle = funcția (particula: Particule): void /// Deplasare lentă într-o singură direcție particle.velocity = Point.polar (50, Math.random () * Math.PI * 2.0); particle.fadeInOut = adevărat; /// [3, 4> secunde de viață particle.startingLife = 3 + Math.random (); particulăModificator = 0,3; /// rotație aleatorie [0, 360> grade particle.rotation = Math.PI * 2.0 * Math.random (); /// Rotiți <-0.5, 0.5] radians per second particle.angularVelocity = (1 - Math.random() * 2) * 0.5; /// Set the scale to [1, 2> particle.scaleX = particle.scaleY = Math.random () + 1; ;

Pentru a adăuga un pic mai multă atmosferă exemplului, am adăugat o textură ușoară care va fi pe stratul superior al scenei; amestecarea sa va fi setată la Multiplica. Efectele particulelor vor fi acum mult mai interesante, deoarece culoarea lor albă de bază va fi schimbată pentru a se potrivi cu lumina, iar scena ca întreg va fi mai integrată.


Îmbunătățirea performanței

O modalitate comună de a optimiza simularea unei mulțimi de particule este de a folosi conceptul de punerea în comun. Împărțirea vă permite să refolosiți obiecte care au fost deja create, dar nu mai sunt necesare.

Conceptul este simplu: când terminăm cu un anumit obiect, îl punem într-o "piscină"; atunci când avem nevoie de un alt obiect de același tip, verificăm mai întâi dacă o rezervă "în schimb" este în piscină. Dacă este, luăm-o și aplicăm noi valori. Putem introduce un anumit număr din aceste obiecte în piscină la începutul simulării pentru a le pregăti mai târziu.

Bacsis: Puteți găsi mai multe informații detaliate despre punerea în comun în acest articol.

Un alt mod în care putem optimiza efectele particulelor este prin precompunerea lor la o textura. Făcând acest lucru veți pierde o mulțime de flexibilitate, dar avantajul este că atrăgând un efect ar fi același lucru cu a desena o singură imagine. Ați anima efectul în același mod ca o animație foaie de sprite obișnuită.


Efectul de particule de incendiu în foaia de sprite

Cu toate acestea, trebuie să fiți atenți: acest lucru nu este adecvat pentru efectele de pe întreg ecranul, cum ar fi zăpada, deoarece ar lua multă memorie.

O modalitate mai ieftină de a simula zăpada ar fi folosirea unei texturi cu mai multe fulgi în interior și apoi o simulare similară cu cea pe care am făcut-o, dar folosind particule mult mai mici. Acest lucru poate fi facut sa arate bine, dar face efort suplimentar.

Iată un exemplu de acest lucru (din scena intro din Fahrenheit, aka Indigo Prophecy):


Gândurile finale

Înainte de a începe să scrieți propriul motor cu particule, trebuie să verificați dacă tehnologia pe care o utilizați pentru a vă face jocul are deja efecte particulare sau dacă există o bibliotecă terță parte. Cu toate acestea, este foarte util să știți cum sunt implementate și, atunci când aveți o bună înțelegere a acestora, nu trebuie să aveți probleme cu utilizarea unei anumite variante, deoarece acestea sunt implementate în mod similar. Motoarele cu particule pot chiar să vină cu editoare care oferă o modalitate WYSIWYG de a-și edita proprietățile.

Dacă efectul de care aveți nevoie poate fi tras de un efect de particule pe bază de sprite, aș recomanda TimelineFX. Acesta poate fi utilizat pentru a crea rapid efecte uimitoare și are o mare bibliotecă de efect pe care o puteți folosi și modifica. Din păcate, nu este instrumentul cel mai intuitiv și nu a fost actualizat de ceva timp.