Gli effetti particellari sono molto comuni nei giochi: è difficile trovare un gioco moderno non lo fa usali. In questo tutorial daremo un'occhiata a come costruire un motore particellare piuttosto complesso e usarlo per creare una divertente scena innevata. Metti il tuo cappello di lana e cominciamo.
Nota: Sebbene questo tutorial sia scritto usando AS3 e Flash, dovresti essere in grado di utilizzare le stesse tecniche e concetti in quasi tutti gli ambienti di sviluppo di giochi.
Fare clic e trascinare il mouse per interagire con l'effetto neve.
Senza il flash? Guarda questo video della demo su YouTube:
L'implementazione demo qui sopra utilizza AS3 e Flash, con Starling Framework per il rendering accelerato GPU. Un'immagine pre-renderizzata della scena 3D (disponibile nel download sorgente) verrà utilizzata come sfondo. Negli strati centrali posizioneremo effetti particellari e l'intera scena sarà moltiplicata per una trama che rappresenta l'attribuzione della luce.
Nei giochi spesso dobbiamo simulare vari fenomeni visivi e di movimento e spesso è necessario visualizzare effetti visivi dinamici come fuoco, fumo e pioggia. Ci sono molti modi in cui possiamo farlo; un metodo comune è usare effetti particellari.
Ciò comporta l'utilizzo di molti piccoli elementi, di solito orientati verso la fotocamera, ma potrebbero essere fatti anche con modelli 3D. L'obiettivo principale è aggiornare la posizione, la scala, il colore e altre proprietà di un gruppo di molte particelle (forse diverse migliaia). Ciò creerà l'illusione di come l'effetto appaia nella vita reale.
In questo tutorial simuleremo due effetti: un effetto neve che consisterà in molti sprites di fiocchi, influenzati dal vento e dalla gravità, e un effetto nebbia sottile che userà una manciata di grandi sprite di fumo.
Gli effetti particellari tipici sono costituiti da uno o più emettitori di particelle. Un emettitore è il luogo da dove provengono le particelle; può assumere varie forme e avere comportamenti diversi. Aggiunge la posizione iniziale e l'angolo delle particelle e può anche definire altri parametri iniziali, come la velocità iniziale.
Creeremo un tipo di emettitore, a emettitore di scatole, che genererà particelle all'interno - avete indovinato - una scatola che definiamo.
Per semplificare l'aggiunta di altri emettitori, utilizzeremo un costrutto di programmazione chiamato an interfaccia che definisce che la classe che la implementa deve avere un insieme definito di metodi. Nel nostro caso, avremo bisogno di un solo metodo:
function generatePosition (): Point
Implementare questo nel box emettitore è super semplice; prendiamo un punto casuale tra i punti minimo e massimo che definiscono la scatola:
public function generatePosition (): Point var randomX: Number = Math.random () * (maxPoint.x - minPoint.x) + minPoint.x; var randomY: Number = Math.random () * (maxPoint.y - minPoint.y) + minPoint.y; restituisce un nuovo punto (randomX, randomY);
Per rendere questo esempio più interessante e un po 'più avanzato aggiungeremo il concetto di: a Collider e fonti di forza.
Un collisore sarà un oggetto costituito da una o più definizioni geometriche, che possono essere collegate a un emettitore di particelle. Quando una particella ha un'interazione con il collisore (cioè quando entra nella geometria) otterremo un evento per decidere cosa vorremmo fare. Questo sarà usato per impedire ai fiocchi di neve di muoversi quando entrano in collisione con il terreno.
Allo stesso modo degli emettitori useremo un'interfaccia che ci impone di implementare la seguente funzione:
la funzione collide (x: Number, y: Number): Boolean;
Nota: questa è una semplice implementazione, quindi stiamo prendendo in considerazione solo la posizione durante il controllo della collisione.
L'implementazione del box collider è semplice; controlliamo se il punto è entro i limiti della scatola:
collide di funzioni pubbliche (x: Number, y: Number): Boolean var xInBounds: Boolean = this.minPoint.x < x && this.maxPoint.x > X; var yInBounds: Boolean = this.minPoint.y < y && this.maxPoint.y > y; return xInBounds && yInBounds;
L'altro tipo di oggetto che introdurremo è una fonte di forza. Ciò avrà un effetto sulla velocità di una particella in base ai suoi parametri e alla posizione e alla massa della particella.
La fonte più semplice sarà chiamata il direzionale forza sorgente, e lo definiremo con un singolo vettore D
(usato per la direzione della forza e la forza). Non prende in considerazione le posizioni delle particelle; applica semplicemente la forza su tutte le particelle da quell'effetto. Con questo saremo in grado di simulare la gravità e il vento - per il vento la direzione varierà nel tempo per sentirsi più realistico.
Un altro tipo di sorgente di forza dipenderà dalla distanza tra un punto e le particelle definiti, allontanandosi sempre più dal centro. Questa fonte sarà definita dalla sua posizione P
e fattore di forza S
. Lo useremo per abilitare l'interazione del mouse con la neve.
Le sorgenti di forza avranno una propria interfaccia, che richiede l'implementazione del seguente metodo:
function forceInPoint (x: Number, y: Number): Point;
Questa volta, tuttavia, abbiamo implementazioni multiple: una per una sorgente di forza di direzione e una per una sorgente di forza di punti.
L'implementazione della sorgente di forza di direzione è la più semplice delle due:
funzione pubblica forceInPoint (x: Number, y: Number): Point /// Ogni particella ottiene lo stesso ritorno di forza new Point (forceVectorX, forceVectorY);
Questa è l'implementazione della sorgente di punti di forza:
/// X. y sono la posizione della funzione pubblica particella forceInPoint (x: Number, y: Number): Point /// Direzione var distanceX: Number = x - positionX; var differenceY: Number = y - positionY; var distance: Number = Math.sqrt (differenceX * differenceX + differenceY * differenceY); /// Valore di falloff che ridurrà la forza forza var falloff: Number = 1.0 / (1.0 + distance); /// Normalizziamo la direzione e usiamo il falloff e la forza per calcolare la forza finale var forceX: Number = differenceX / distance * falloff * strength; var forceY: Number = differenceY / distance * falloff * strength; restituisce un nuovo punto (forceX, forceY);
Si noti che questo metodo verrà chiamato continuamente. Questo ci consente di modificare i parametri di forza quando l'effetto è attivo. Useremo questa funzione per simulare il vento e aggiungere l'interattività del mouse.
La parte principale dell'implementazione è nel Effetto
classe, che è responsabile della generazione e dell'aggiornamento delle particelle.
La quantità di particelle da deporre sarà determinata dal spawnPerSecond
valore nel aggiornare
metodo:
_spawnCounter - = tempo; /// Utilizzo di un ciclo per generare più particelle in un fotogramma mentre (_spawnCounter <= 0) /// Spawn the number of particles according to the passed time _spawnCounter += (1 / _spawPerSecond) * time; spawnParticle();
L'aggiornamento è un po 'più complesso. Prima l'implementazione aggiorna le forze, quindi richiama l'aggiornamento della simulazione delle particelle e verifica le collisioni. È anche responsabile della rimozione delle particelle quando non sono più necessarie.
var i: int = 0; /// utilizzando il ciclo while in modo da poter rimuovere le particelle dal contenitore mentre (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++;
Non ho ancora menzionato la parte più importante dell'implementazione: come rappresentiamo le particelle. Il particella
la classe erediterà da un oggetto che possiamo visualizzare (immagine) e avremo alcune proprietà che ne influenzeranno la modifica durante l'aggiornamento:
startingLife
- per quanto tempo una particella può rimanere in vita.mobile
- se la posizione della particella sia cambiata (usata per congelare le particelle in posizione).velocità
- quanto la particella si muoverà in un determinato intervallo di tempo.accelerazione
- quanto la velocità della particella cambierà in un determinato intervallo di tempo.velocità angolare
- quanto velocemente la rotazione cambia in un determinato periodo di tempo.fadeInOut
- se usiamo un valore alfa fading per creare e distruggere senza problemi la particella.alphaModifier
- determina il valore alfa di base.massa
- la massa fisica della particella (usata quando si calcola l'accelerazione dalle forze).Ogni particella ha un aggiornare
funzione che viene chiamata con delta temporale (dt). Vorrei mostrare la parte di questa funzione relativa all'aggiornamento della posizione delle particelle, che è comune nei giochi:
/// aggiorna la posizione con velocity x + = _velocity.x * dt; y + = _velocity.y * dt; /// velocità di aggiornamento con accelerazione _velocity.x + = _acceleration.x * dt; _velocity.y + = _acceleration.y * dt;
Questo viene fatto usando l'integrazione di Eulero e ha errori di accuratezza, ma dal momento che lo stiamo usando solo per effetti visivi questi non ci infastidiranno. Se stai facendo simulazioni fisiche importanti per il gameplay dovresti esaminare altri metodi.
Finalmente siamo arrivati al punto in cui spiegherò come implementare l'effetto reale. Per fare un nuovo effetto, estenderemo il Effetto
classe.
Inizieremo con l'effetto neve. In primo luogo, posiziona un emettitore di casella nella parte superiore dello schermo e usalo per generare parecchie particelle. Un collisore verrà utilizzato per rilevare se una particella ha raggiunto il pavimento, nel qual caso ne imposteremo la sua mobile
proprietà a falso
.
La cosa importante da accertare è che le particelle siano abbastanza casuali da non creare pattern visibili sullo schermo, il che danneggia l'illusione. Lo facciamo in due modi:
Inizializziamo ogni particella di neve in questo modo:
particle.fadeInOut = true; /// Life [3, 4> seconds particle.startingLife = 3 + Math.random (); /// Piccola quantità di velocità iniziale particle.velocity = Point.polar (30, Math.random () * Math.PI * 2.0); /// Rotazione casuale [0, 360> degrees particle.rotation = Math.PI * 2.0 * Math.random (); /// Scala casuale [0.5, 1> particle.scaleX = particle.scaleY = Math.random () * 0.5 + 0.5;
Per dare loro un movimento realistico di caduta dal cielo useremo una sorgente di forza direzionale come gravità. Sarebbe troppo facile fermarsi qui, quindi aggiungeremo un'altra forza direzionale per simulare il vento, che varierà nel tempo.
/// -20 è un numero arbitrario che ha funzionato bene durante il test /// (9,81m / s / s è l'effettiva accelerazione dovuta alla gravità sulla Terra) var gravità: DirectionalField = new DirectionalField (0, -9,81 * -20); /// L'inizializzazione non è importante; i valori cambieranno nel tempo _wind = new DirectionalField (1, 0); /// imposta le forze sull'effetto this.forces = new[gravità, _wind];
Variamo il valore del vento usando una funzione seno; questo è stato per lo più determinato attraverso la sperimentazione. Per l'asse x alziamo il seno alla potenza di 4, rendendo il suo picco più acuto. Ogni sei secondi ci sarà un picco, producendo l'effetto di una forte raffica di vento. Sull'asse delle ordinate il vento oscillerà rapidamente tra -20 e 20.
/// Calcola la forza del vento _counter + = tempo; _wind.forceVectorX = Math.pow (Math.sin (_counter) * 0.5 + 0.5, 4) * 150; _wind.forceVectorY = Math.sin (_counter * 100) * 20;
Dai un'occhiata alla trama delle funzioni per capire meglio cosa sta succedendo.
Per completare l'effetto, aggiungeremo un effetto nebbia sottile, utilizzando un emettitore di box che copre l'intera scena.
Dato che la trama che useremo per la particella è relativamente grande, l'emettitore verrà impostato per generare un piccolo numero di particelle. Il livello alfa della particella sarà inizialmente basso per impedirgli di oscurare completamente la scena. Li imposteremo anche per ruotare lentamente, al fine di simulare un effetto del vento.
/// Coprire gran parte dello schermo this.emitter = new Box (0, 40, 640, 400); /// Vogliamo solo alcune delle particelle sullo schermo alla volta this.spawnPerSecond = 0.05; this.setupParticle = function (particle: Particle): void /// Sposta lentamente in una direzione particle.velocity = Point.polar (50, Math.random () * Math.PI * 2.0); particle.fadeInOut = true; /// [3, 4> secondi della vita particle.startingLife = 3 + Math.random (); particle.alphaModifier = 0,3; /// Rotazione casuale [0, 360> degrees particle.rotation = Math.PI * 2.0 * Math.random (); /// Ruota <-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; ;
Per aggiungere un po 'più di atmosfera all'esempio, ho aggiunto una texture leggera che sarà sul livello superiore della scena; la sua miscelazione sarà impostata su Moltiplicare. Gli effetti particellari ora saranno molto più interessanti, dal momento che il loro colore di base bianco verrà modificato per adattarsi alla luce, e la scena nel suo complesso si sentirà più integrata.
Un modo comune per ottimizzare la simulazione di molte particelle è usare il concetto di messa in comune. Il raggruppamento consente di riutilizzare oggetti che sono già stati creati, ma non sono più necessari.
Il concetto è semplice: quando abbiamo finito con un determinato oggetto, lo mettiamo in un "pool"; quindi, quando abbiamo bisogno di un altro oggetto dello stesso tipo, prima controlliamo per vedere se c'è un "ricambio" nel pool. Se lo è, lo prendiamo e applichiamo nuovi valori. Possiamo inserire un certo numero di questi oggetti nel pool all'inizio della simulazione per prepararli per dopo.
Mancia: Puoi trovare informazioni più dettagliate sul raggruppamento in questo articolo.Un altro modo in cui possiamo ottimizzare gli effetti particellari è precomputerli a una trama. Facendo ciò perderete molta flessibilità, ma il vantaggio è che disegnare un effetto equivale a disegnare una singola immagine. Dovresti animare l'effetto nello stesso modo di un'animazione normale di un foglio di sprite.
Tuttavia, è necessario fare attenzione: questo non è adatto per effetti a tutto schermo come la neve, poiché richiederebbe molta memoria.
Un modo più economico per simulare la neve consisterebbe nell'usare una texture con più fiocchi all'interno, e quindi fare una simulazione simile a quella che abbiamo fatto, ma usando molte meno particelle. Questo può essere fatto per sembrare buono, ma richiede uno sforzo supplementare.
Ecco un esempio di ciò (dalla scena introduttiva di Fahrenheit, alias Indigo Prophecy):
Prima di iniziare a scrivere il tuo motore particellare, dovresti controllare se la tecnologia che stai usando per rendere il tuo gioco funzioni già effetti particellari o se esiste una libreria di terze parti. Tuttavia, è davvero utile sapere come sono implementati e, quando ne hai una buona comprensione, non dovresti avere problemi ad usare una particolare variante, poiché sono implementate in modo simile. I motori di particelle possono persino venire con editor che forniscono un modo WYSIWYG per modificare le loro proprietà.
Se l'effetto di cui hai bisogno può essere tirato in un effetto particella basato sul foglio sprite, ti consiglio TimelineFX. Può essere utilizzato per creare rapidamente effetti sorprendenti e ha una grande libreria di effetti che è possibile utilizzare e modificare. Sfortunatamente, non è lo strumento più intuitivo e non è stato aggiornato da un po 'di tempo.