Lag en koselig, snøaktig nattscene ved hjelp av partikkeleffekter

Partikkeleffekter er svært vanlige i spill - det er vanskelig å finne et moderne spill som ikke bruk dem. I denne opplæringen skal vi ta en titt på hvordan å bygge en ganske komplisert partikkelmotor og bruke den til å skape en morsom snø scene. Sett ullhatten på og la oss begynne.

Merk: Selv om denne opplæringen er skrevet med AS3 og Flash, bør du kunne bruke de samme teknikkene og konseptene i nesten hvilket som helst spillutviklingsmiljø.


Endelig resultatforhåndsvisning

Klikk og dra musen for å snakke med snøeffekten.

Ingen Flash? Sjekk ut denne videoen av demoen på YouTube:


Setup

Demo implementeringen ovenfor bruker AS3 og Flash, med Starling Framework for GPU akselerert rendering. Et pre-gjengitt bilde av 3D-scene (tilgjengelig i kilde nedlasting) vil bli brukt som bakgrunn. I mellomlagene plasserer vi partikkeleffekter, og hele scenen vil bli multiplisert med en tekstur som representerer lysattribusjon.


Partikkeleffekter

I spill må vi ofte simulere ulike visuelle og bevegelsesfenomener, og må ofte vise dynamiske visuelle effekter som brann, røyk og regn. Det er flere måter vi kan gjøre dette på; en vanlig metode er å bruke partikkel effekter.

Dette innebærer å bruke mange små elementer - vanligvis bilder orientert mot kameraet - men de kan også gjøres med 3D-modeller. Hovedmålet er å oppdatere posisjon, skala, farge og andre egenskaper av en gruppe med mange partikler (kanskje flere tusen). Dette vil skape en illusjon av hvordan virkningen ser ut i virkeligheten.


Moderne partikkeleffekter: GPU-partikler i den nye Unreal Engine 4

I denne opplæringen simulerer vi to effekter: en snøeffekt som vil bestå av mange flake sprites, påvirket av vind og tyngdekraften, og en subtile tåkeeffekt som vil bruke en håndfull store røykspriter.


Implementere Emittere, Colliders og Force Kilder

Typiske partikkeleffekter består av en eller flere partikkelemittere. En emitter er stedet hvor partikler kommer fra; Det kan ta forskjellige former og ha forskjellige oppførsel. Den legger til startposisjon og vinkel på partikler, og kan også definere andre startparametere, for eksempel innledende hastighet.

Vi lager en type emitter, a boksen emitter, som vil gyte partikler innenfor - du har gjettet det - en boks som vi definerer.

For å gjøre det enkelt for oss å legge til flere emittere, bruker vi en programmeringskonstruksjon kalt an grensesnitt som definerer at klassen som implementerer den må ha et definert sett med metoder. I vårt tilfelle krever vi bare én metode:

 funksjon genererePosisjon (): Punkt

Implementering av dette i boksenemitteren er super enkel; vi tar et tilfeldig punkt mellom minimum og maksimum poeng som definerer boksen:

 offentlig funksjon generatePosition (): Punkt var randomX: Number = Math.random () * (maxPoint.x - minPoint.x) + minPoint.x; var randomY: Nummer = Math.random () * (maxPoint.y - minPoint.y) + minPoint.y; returnere nytt punkt (randomX, randomY); 

For å gjøre dette eksemplet mer interessant og litt mer avansert, legger vi til konseptet om: a Collider og tvinge kilder.

En collider vil være et objekt bestående av en eller flere geometridefinisjoner, som kan festes til en partikkelemitter. Når en partikkel har en interaksjon med collider (det vil si når den går inn i geometrien), får vi en hendelse for å bestemme hva vi ønsker å gjøre. Dette vil bli brukt til å stoppe snøflakene fra å bevege seg når de kolliderer med bakken.

På samme måte som med emitterne bruker vi et grensesnitt som krever at vi implementerer følgende funksjon:

 funksjonskollider (x: tall, y: tall): boolsk;

Merk: Dette er en enkel implementering, så vi tar bare hensyn til stillingen når du sjekker for kollisjon.

Implementeringen av boksen collider er enkel; vi sjekker om punktet ligger innenfor rammen av boksen:

 offentlig funksjon kolliderer (x: tall, y: tall): boolsk var xInBounds: Boolean = this.minPoint.x < x && this.maxPoint.x > x; var yInBounds: Boolean = this.minPoint.y < y && this.maxPoint.y > y; returnere xInBounds && yInBounds; 

Den andre typen objekt vi skal introdusere er en kraftkilde. Dette vil påvirke partikkelhastigheten basert på parametrene og partikkelens posisjon og masse.

Den enkleste kilden vil bli kalt retnings tvinge kilde, og vi vil definere den med en enkelt vektor D (brukes til kraftretning og styrke). Det tar ikke hensyn til partikkels posisjoner; det gjelder bare kraften på alle partiklene fra den effekten. Med dette vil vi kunne simulere tyngdekraft og vind - for vinden vil retningen variere i tide for å føle seg mer realistisk.

En annen type kraftkilde vil avhenge av avstanden mellom et definert punkt og partikler, blir svakere lenger unna senteret. Denne kilden vil bli definert av sin posisjon P og styrkefaktor S. Vi bruker dette til å aktivere musens interaksjon med snøen.


Typer kraftkilder vi skal opprette

Kraftkildene vil ha sitt eget grensesnitt, og krever at følgende metode implementeres:

 funksjonskraftInPoint (x: tall, y: tall): punkt;

Denne gangen har vi imidlertid flere implementeringer: en for en retningskraftkilde og en for en punktkildekilde.

Kraftkilden implementering er den enklere av de to:

 offentlig funksjon forceInPoint (x: tall, y: tall): Punkt /// Hver partikkel får samme kraft returnerer nytt punkt (forceVectorX, forceVectorY); 

Dette er punktkilden implementering:

 /// x. y er posisjonen til partikkel-offentlig funksjon forceInPoint (x: tall, y: tall): Punkt /// Retning og avstand var forskjellX: Tall = x - posisjonX; var forskjell: Nummer = y - posisjonY; var avstand: Nummer = Math.sqrt (differanseX * differanseX + forskjell * forskjell); /// Falloff verdi som vil redusere kraftstyrke var falloff: Number = 1.0 / (1.0 + avstand); /// Vi normaliserer retningen, og bruk avgang og styrke for å beregne endelig kraft var forceX: Number = differenceX / avstand * falloff * styrke; var forceY: Nummer = differanse / avstand * falloff * styrke; returnere nytt punkt (forcex, forcey); 

Merk at denne metoden vil bli kalt kontinuerlig. Dette gjør at vi kan endre kraftparametere når effekten er aktiv. Vi bruker denne funksjonen til å simulere vinden og legge til musens interaktivitet.


Effekt og partikkelimplementering

Hoveddelen av implementeringen er i Effekt klassen, som er ansvarlig for gyting og oppdatering av partikler.

Mengden partikler som skal gyte bestemmes av spawnPerSecond verdi i Oppdater metode:

 _spawnCounter - = time; /// Bruk en sløyfe til å gyte flere partikler i en ramme mens (_spawnCounter <= 0)  /// Spawn the number of particles according to the passed time _spawnCounter += (1 / _spawPerSecond) * time; spawnParticle(); 

Oppdatering er litt mer komplisert. Først implementerer oppdateringen kreftene, så kaller det partikkel simuleringsoppdateringen og sjekker for kollisjoner. Det er også ansvarlig for å fjerne partikler når de ikke lenger behøves.

 var jeg: int = 0; /// bruk mens løkke slik at vi kan fjerne partiklene fra beholderen mens (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++;  

Jeg har ikke nevnt den viktigste delen av implementeringen: hvordan vi representerer partikler. De partikkel~~POS=TRUNC klassen vil arve fra en gjenstand vi kan vise (bilde) og har noen egenskaper som vil påvirke endringen under oppdateringen:

  • startingLife - hvor lenge en partikkel kan holde seg i live.
  • bevegelig - om partikkelens posisjon endres (brukes til å fryse partikkelen på plass).
  • hastighet - hvor mye partikkelen vil bevege seg over en viss tid.
  • akselerasjon - hvor mye partikkelens hastighet vil endres i en viss tid.
  • vinkelhastighet - hvor raskt rotasjonen endres i et bestemt tidsrom.
  • fadeInOut - om vi bruker en fading alfa-verdi for å jevne opp og ødelegge partikkelen.
  • alphaModifier - bestemmer basalfa-verdien.
  • masse - partikkelens fysiske masse (brukt ved beregning av akselerasjon fra krefter).

Hver partikkel har en Oppdater funksjon som kalles time delta (dt). Jeg vil gjerne vise den delen av den funksjonen som omhandler oppdatering av partikkelposisjon, som er vanlig i spill:

 /// oppdateringsposisjon med hastighet x + = _velocity.x * dt; y + = _velocity.y * dt; /// oppdater hastighet med akselerasjon _velocity.x + = _acceleration.x * dt; _velocity.y + = _acceleration.y * dt;

Dette er gjort ved hjelp av Euler-integrasjon, og det har nøyaktighetsfeil, men siden vi bruker det bare for visuelle effekter, vil det ikke plage oss. Hvis du gjør fysikkimuleringer som er viktige for spillingen, bør du se på andre metoder.


Eksempel Effekter

Til slutt har vi kommet til det punktet hvor jeg skal forklare hvordan jeg skal implementere den virkelige effekten. For å lage en ny effekt vil vi utvide Effekt klasse.


Partikkel teksturer

La det snø

Vi starter med snøeffekten. Først legger du en boksemitter på toppen av skjermen, og bruker den til å gyte ganske mange partikler. En collider vil bli brukt til å oppdage om en partikkel har nådd gulvet, i så fall vil vi sette den bevegelig eiendom til falsk.

Det viktige vi må sikre er at partiklene er tilfeldig nok slik at de ikke lager synlige mønstre på skjermen, noe som ødelegger illusjonen. Vi gjør dette på et par måter:

  • Tilfeldig starthastighet - hver partikkel vil bevege seg litt annerledes.
  • Tilfeldig skala - annet enn størrelser er annerledes, dette gir også mer dybde til effekten slik at den ser mer 3D ut.
  • Tilfeldig rotasjon - gjør hver partikkel effektivt unikt, selv om de bruker det samme bildet.

Vi initialiserer hver snøpartikkel på denne måten:

 particle.fadeInOut = true; /// Liv [3, 4> sekunder particle.startingLife = 3 + Math.random (); /// Liten mengde starthastighet particle.velocity = Point.polar (30, Math.random () * Math.PI * 2.0); /// Tilfeldig rotasjon [0, 360> grader particle.rotation = Math.PI * 2.0 * Math.random (); /// Tilfeldig skala [0,5, 1> particle.scaleX = particle.scaleY = Math.random () * 0,5 + 0,5;

For å gi dem en realistisk bevegelse for å falle fra himmelen, vil vi bruke en retningskraftkilde som tyngdekraften. Det ville være for enkelt å stoppe her, så vi skal legge til en annen retningskraft for å simulere vind, som vil variere i tide.

 /// -20 er vilkårlig tall som fungerte bra når testing /// (9.81m / s / s er den faktiske akselerasjonen på grunn av tyngdekraften på jorden) var tyngdekraften: DirectionalField = new DirectionalField (0, -9,81 * -20); /// Initialisering er ikke viktig; verdiene vil endres i tid _wind = new DirectionalField (1, 0); /// sette krefter på effekten this.forces = new [tyngdekraft, vind]

Vi vil variere vindverdien med en sinusfunksjon; dette ble for det meste bestemt gjennom eksperimentering. For x-aksen øker vi sinus til kraften på 4, noe som gjør toppen skarpere. Hvert seks sekunder vil det være en topp som gir effekten av et sterkt vindstød. På y-aksen vil vinden raskt svinge mellom -20 og 20.

 /// Beregn vindkraft _counter + = tid; _wind.forceVectorX = Math.pow (Math.sin (_counter) * 0,5 + 0,5, 4) * 150; _wind.forceVectorY = Math.sin (_counter * 100) * 20;

Ta en titt på funksjonsplottet for å få en bedre forståelse av hva som skjer.


X-aksen representerer tid; y-aksen representerer vindhastighet. (Ikke skalert.)

Legg til litt tåke

For å fullføre effekten skal vi legge til en subtile tåkeffekt ved å bruke en boksemitter som dekker hele scenen.

Siden tekstur vi skal bruke til partikkelen er relativt stor, vil emitteren settes til å gyte et lite antall partikler. Alfinnivået av partikkelen vil fra begynnelsen være lavt for å forhindre at den helt skjuler scenen. Vi vil også sette dem til å rotere sakte, for å simulere en vind effekt.

 /// Dekker stor del av skjermen this.emitter = ny boks (0, 40, 640, 400); /// Vi vil ha bare noen få partikler på skjermen om gangen this.spawnPerSecond = 0.05; this.setupParticle = funksjon (partikkel: Partikkel): void /// Flytt sakte i en retning particle.velocity = Point.polar (50, Math.random () * Math.PI * 2.0); particle.fadeInOut = true; /// [3, 4> sekunder av livet particle.startingLife = 3 + Math.random (); partikkel.alphaModifier = 0,3; /// Tilfeldig rotasjon [0, 360> grader particle.rotation = Math.PI * 2.0 * Math.random (); /// Rotere <-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; ;

For å legge til litt mer atmosfære i eksemplet, har jeg lagt til en lys tekstur som vil være på toppscenen lag; blandingen blir satt til Multiplisere. Partikkeleffektene vil nå være mye mer interessante, siden deres basiske hvite farge vil bli endret for å matche lyset, og scenen som helhet vil føles mer integrert.


Forbedre ytelsen

En vanlig måte å optimalisere simuleringen av mange partikler på er å bruke konseptet av bading. Pooling gjør at du kan gjenbruke gjenstander som allerede er opprettet, men er ikke lenger nødvendige.

Konseptet er enkelt: når vi er ferdige med et bestemt objekt, legger vi det i et "basseng"; da, når vi trenger et annet objekt av samme type, kontrollerer vi først for å se om en "spare" på er i bassenget. Hvis det er, tar vi det bare og bruker nye verdier til det. Vi kan sette inn et bestemt antall av disse objektene i bassenget ved starten av simuleringen for å forberede dem til senere.

Tips: Du finner mer detaljert informasjon om samling i denne artikkelen.

En annen måte å optimalisere partikkeleffekter på er å precomputing dem til en tekstur. Ved å gjøre dette vil du miste mye fleksibilitet, men fordelen er at tegning av en effekt ville være den samme som å tegne et enkelt bilde. Du ville animere effekten på samme måte som en vanlig sprite ark animasjon.


Brannpartikkel effekt i sprite ark form

Men du må være forsiktig: Dette er ikke godt egnet for fullskjermseffekter som snø, siden de ville ta mye minne.

En billigere måte å simulere snø ville være å bruke en tekstur med flere flager inni, og deretter gjøre en lignende simulering til den vi gjorde, men ved å bruke langt mindre partikler. Dette kan gjøres for å se bra ut, men tar ekstra innsats.

Her er et eksempel på det (fra intro-scenen til Fahrenheit, aka Indigo Prophecy):


Siste tanker

Før du begynner å skrive din egen partikkelmotor, bør du sjekke om teknologien du bruker for å gjøre spillet ditt allerede med partikkeleffekter, eller om et tredjepartsbibliotek eksisterer. Likevel er det veldig nyttig å vite hvordan de implementeres, og når du har en god forståelse for det, bør du ikke ha problemer med å bruke en bestemt variant, siden de implementeres på lignende måte. Partikkelmotorer kan til og med komme med redaktører som gir en WYSIWYG-måte å redigere egenskapene til.

Hvis effekten du trenger kan trekkes av i en sprite-arkbasert partikkel-effekt, vil jeg anbefale TimelineFX. Det kan brukes til å lage fantastiske effekter raskt, og har et stort bibliotek med effekt du kan bruke og endre. Dessverre er det ikke det mest intuitive verktøyet, og har ikke blitt oppdatert om en stund.