Créer une scène de nuit confortable et enneigée à l'aide d'effets de particules

Les effets de particules sont très courants dans les jeux - il est difficile de trouver un jeu moderne qui ne pas Utilise les. Dans ce tutoriel, nous allons voir comment construire un moteur de particules assez complexe et l'utiliser pour créer une scène enneigée amusante. Mets ton bonnet en laine et commençons.

Remarque: Bien que ce tutoriel ait été écrit avec AS3 et Flash, vous devriez pouvoir utiliser les mêmes techniques et concepts dans presque tous les environnements de développement de jeux..


Aperçu du résultat final

Cliquez et faites glisser la souris pour interagir avec l'effet de neige.

Pas de flash? Regardez cette vidéo de la démo sur YouTube:


Installer

L'implémentation de démonstration ci-dessus utilise AS3 et Flash, avec le rendu accéléré de Starling Framework for GPU. Une image pré-rendue de la scène 3D (disponible dans le téléchargement de la source) sera utilisée comme arrière-plan. Dans les couches intermédiaires, nous placerons des effets de particules et la scène entière sera multipliée par une texture représentant l'attribution de la lumière..


Effets de particules

Dans les jeux, nous devons souvent simuler divers phénomènes visuels et de mouvement et souvent afficher des effets visuels dynamiques tels que le feu, la fumée et la pluie. Il y a plusieurs façons de le faire. une méthode courante consiste à utiliser des effets de particules.

Cela implique d’utiliser beaucoup de petits éléments - généralement des images orientées face à la caméra - mais ils pourraient également être réalisés à l’aide de modèles 3D. L'objectif principal est de mettre à jour la position, l'échelle, la couleur et d'autres propriétés d'un groupe de nombreuses particules (peut-être plusieurs milliers). Cela créera une illusion sur l’effet de l’effet dans la vie réelle.


Effets de particules modernes: particules GPU dans le nouveau Unreal Engine 4

Dans ce didacticiel, nous simulerons deux effets: un effet de neige composé de nombreux sprites en flocons affectés par le vent et la gravité, et un effet de brouillard subtil utilisant une poignée de gros sprites de fumée..


Mise en œuvre d'émetteurs, de collisionneurs et de sources de force

Les effets de particules typiques consistent en un ou plusieurs émetteurs de particules. Un émetteur est l'endroit d'où proviennent les particules; il peut prendre différentes formes et avoir différents comportements. Il ajoute la position initiale et l'angle des particules, et peut également définir d'autres paramètres de départ, tels que la vitesse initiale.

Nous allons créer un type d'émetteur, un émetteur de boîte, qui va engendrer des particules à l'intérieur - vous l'avez deviné - une boîte que nous définissons.

Pour faciliter l'ajout d'émetteurs, nous allons utiliser une structure de programmation appelée interface qui définit que la classe l'implémentant doit avoir un ensemble défini de méthodes. Dans notre cas, nous n’aurons besoin que d’une méthode:

 function generatePosition (): Point

La mise en œuvre de cet émetteur dans la boîte est super simple; on prend un point aléatoire entre les points minimum et maximum qui définissent la boîte:

 Fonction publique generatePosition (): Point var randomX: Number = Math.random () * (maxPoint.x - minPoint.x) + minPoint.x; var randomY: Number = Math.random () * (maxPoint.y - minPoint.y) + minPoint.y; renvoyer un nouveau point (randomX, randomY); 

Pour rendre cet exemple plus intéressant et un peu plus avancé, nous allons ajouter le concept de: a collisionneur et sources de force.

Un collisionneur sera un objet consistant en une ou plusieurs définitions de géométrie, pouvant être attachées à un émetteur de particules. Lorsqu'une particule a une interaction avec le collisionneur (c'est-à-dire lorsqu'elle entre dans la géométrie), un événement détermine ce que nous voulons faire. Cela servira à empêcher les flocons de neige de bouger lorsqu'ils entreront en collision avec le sol.

De la même manière que pour les émetteurs, nous utiliserons une interface qui nous oblige à implémenter la fonction suivante:

 la fonction entre en collision (x: Number, y: Number): Boolean;

Remarque: il s’agit d’une implémentation simple, nous ne prenons donc en compte que la position lors de la vérification des collisions..

La mise en œuvre du collisionneur de boîte est simple; nous vérifions si le point est dans les limites de la boîte:

 fonction publique se heurte (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'autre type d'objet que nous introduirons est une source de force. Cela aura un effet sur la vitesse d'une particule en fonction de ses paramètres, de sa position et de sa masse..

La source la plus simple s'appellera la directionnel source de force, et nous le définirons avec un seul vecteur (utilisé pour la direction et la force de la force). Il ne prend pas en compte les positions des particules; il applique simplement la force sur toutes les particules de cet effet. Avec cela, nous pourrons simuler la gravité et le vent - pour le vent, la direction variera dans le temps afin de donner une impression plus réaliste..

Un autre type de source de force dépendra de la distance entre un point défini et des particules, s’affaiblissant plus loin du centre. Cette source sera définie par sa position P et facteur de force S. Nous allons utiliser cela pour permettre une interaction de la souris avec la neige.


Types de sources de force que nous allons créer

Les sources de force auront leur propre interface, nécessitant la mise en œuvre de la méthode suivante:

 fonction forceInPoint (x: Number, y: Number): Point;

Cette fois, cependant, nous avons plusieurs implémentations: une pour une source de force directionnelle et une pour une source de force ponctuelle..

La mise en œuvre de la source de force directionnelle est la plus simple des deux:

 fonction publique forceInPoint (x: Number, y: Number): Point /// Chaque particule reçoit la même force retour new Point (forceVectorX, forceVectorY); 

Voici l'implémentation de la source ponctuelle:

 /// X. y est la position de la fonction publique de particule forceInPoint (x: Number, y: Number): Point /// Sens et distance var differenceX: Number = x - positionX; var differenceY: Nombre = y - positionY; var distance: Number = Math.sqrt (differenceX * differenceX + differenceY * differenceY); /// Valeur d'atténuation qui réduira la force de la force var falloff: Number = 1.0 / (1.0 + distance); /// Nous normalisons la direction et utilisons l'atténuation et la force pour calculer la force finale var forceX: Nombre = différenceX / distance * atténuation * force; var forceY: Nombre = différenceY / distance * chute * force; renvoyer un nouveau point (forceX, forceY); 

Notez que cette méthode sera appelée en permanence. Cela nous permet de modifier les paramètres de force lorsque l'effet est actif. Nous allons utiliser cette fonctionnalité pour simuler le vent et ajouter de l'interactivité à la souris.


Mise en oeuvre des effets et des particules

La partie principale de la mise en œuvre est dans le Effet classe, responsable du frai et de la mise à jour des particules.

La quantité de particules à engendrer sera déterminée par le spawnPerSecond valeur dans le mettre à jour méthode:

 _spawnCounter - = heure; /// Utiliser une boucle pour générer plusieurs particules dans une image while (_spawnCounter <= 0)  /// Spawn the number of particles according to the passed time _spawnCounter += (1 / _spawPerSecond) * time; spawnParticle(); 

La mise à jour est un peu plus complexe. L'implémentation met d'abord à jour les forces, puis appelle la mise à jour de la simulation de particules et vérifie les collisions. Il est également responsable de l'élimination des particules lorsqu'elles ne sont plus nécessaires..

 var i: int = 0; /// en utilisant la boucle while afin que nous puissions éliminer les particules du conteneur while (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++;  

Je n'ai pas encore mentionné la partie la plus importante de la mise en œuvre: comment nous représentons les particules. le Particule La classe héritera d'un objet que nous pouvons afficher (image) et aura certaines propriétés qui affecteront son changement lors de la mise à jour:

  • départVie - combien de temps une particule peut rester en vie.
  • mobile - si la position de la particule doit être modifiée (utilisée pour geler la particule en place).
  • rapidité - combien la particule bougera dans un laps de temps déterminé.
  • accélération - combien la vitesse de la particule changera dans un laps de temps déterminé.
  • vitesse angulaire - à quelle vitesse la rotation change dans un laps de temps donné.
  • Disparaître - si nous utilisons une valeur alpha atténuée pour créer et détruire la particule en douceur.
  • alphaModifier - détermine la valeur alpha de base.
  • Masse - la masse physique de la particule (utilisée pour calculer l'accélération à partir des forces).

Chaque particule a un mettre à jour fonction appelée avec delta temporel (dt). Je voudrais montrer la partie de cette fonction traitant de la mise à jour de la position des particules, ce qui est courant dans les jeux:

 /// met à jour la position avec la vitesse x + = _velocity.x * dt; y + = _velocity.y * dt; /// met à jour la vitesse avec accélération _velocity.x + = _acceleration.x * dt; _velocity.y + = _acceleration.y * dt;

Ceci est fait en utilisant l'intégration d'Euler et il y a des erreurs de précision, mais comme nous l'utilisons uniquement pour des effets visuels, cela ne nous dérangera pas. Si vous effectuez des simulations physiques importantes pour le jeu, vous devriez envisager d'autres méthodes..


Exemple d'effets

Enfin, nous en arrivons au point où je vais expliquer comment appliquer l’effet réel. Pour faire un nouvel effet, nous étendrons la Effet classe.


Textures de particules

Laissez-le neige

Nous allons commencer par l'effet de la neige. Tout d’abord, placez un émetteur de boîte sur l’écran et utilisez-le pour générer plusieurs particules. Un collisionneur sera utilisé pour détecter si une particule a atteint le sol. Dans ce cas, nous définirons sa mobile propriété à faux.

La chose importante que nous devons nous assurer est que les particules sont suffisamment aléatoires pour ne pas créer de motifs visibles sur l’écran, ce qui endommagerait l’illusion. Nous faisons cela de plusieurs manières:

  • Vitesse de départ aléatoire - chaque particule se déplacera de manière légèrement différente.
  • Échelle aléatoire - outre le fait que les tailles soient différentes, cela ajoute plus de profondeur à l'effet, ce qui lui donne un aspect plus 3D..
  • Rotation aléatoire - donne à chaque particule un aspect unique, même si elle utilise la même image.

Nous initialisons chaque particule de neige de cette façon:

 particle.fadeInOut = true; /// Vie [3, 4> secondes de particule.startingLife = 3 + Math.random (); /// Petite quantité de vitesse de départ particule.velocity = Point.polar (30, Math.random () * Math.PI * 2.0); /// Rotation aléatoire [0, 360> degrés particle.rotation = Math.PI * 2.0 * Math.random (); /// Échelle aléatoire [0.5, 1> particule.échelleX = particule.échelleY = Math.random () * 0,5 + 0,5;

Pour leur donner un mouvement réaliste de chute du ciel, nous utiliserons une source de force directionnelle comme gravité. Il serait trop facile de s’arrêter ici. Nous allons donc ajouter une autre force directionnelle pour simuler le vent, qui variera dans le temps..

 /// -20 est un nombre arbitraire qui a bien fonctionné lors des tests /// (9,81 m / s est l'accélération réelle due à la gravité sur Terre) var gravité: DirectionalField = new DirectionalField (0, -9,81 * -20); /// L'initialisation n'est pas importante; les valeurs changeront avec le temps _wind = new DirectionalField (1, 0); /// définir les forces à l'effet this.forces = new [gravité, _ vent];

Nous allons faire varier la valeur du vent en utilisant une fonction sinusoïdale; cela a été principalement déterminé par l'expérimentation. Pour l’axe des x, nous élevons le sinus à la puissance de 4, ce qui rend son pic plus net. Toutes les six secondes, il y aura un pic produisant l'effet d'une forte rafale de vent. Sur l'axe des y, le vent va rapidement osciller entre -20 et 20.

 /// Calculer la force du vent _counter + = time; _wind.forceVectorX = Math.pow (Math.sin (_counter) * 0.5 + 0.5, 4) * 150; _wind.forceVectorY = Math.sin (_counter * 100) * 20;

Jetez un coup d'œil au diagramme de fonctions pour mieux comprendre ce qui se passe.


L'axe des x représente le temps; l'axe des y représente la vitesse du vent. (Pas à l'échelle.)

Ajouter du brouillard

Pour compléter l'effet, nous allons ajouter un effet de brouillard subtil, en utilisant un émetteur de boîte qui couvre toute la scène..

Étant donné que la texture que nous allons utiliser pour la particule est relativement grosse, l'émetteur sera configuré pour générer un petit nombre de particules. Le niveau alpha de la particule sera dès le début bas pour l'empêcher de complètement obscurcir la scène. Nous allons également les mettre en rotation lente afin de simuler un effet de vent.

 /// Couvre une grande partie de l'écran this.emitter = new Box (0, 40, 640, 400); /// Nous voulons seulement quelques particules à l'écran à la fois this.spawnPerSecond = 0.05; this.setupParticle = fonction (particule: Particle): void /// Déplacement lent dans un sens particule.velocity = Point.polar (50, Math.random () * Math.PI * 2.0); particle.fadeInOut = true; /// [3, 4> secondes de la vie particule.startantLife = 3 + Math.random (); particule.alphaModifier = 0,3; /// Rotation aléatoire [0, 360> degrés particle.rotation = Math.PI * 2.0 * Math.random (); /// Rotation <-0.5, 0.5] radians per second particle.angularVelocity = (1 - Math.random() * 2) * 0.5; /// Set the scale to [1, 2> particule.échelleX = particule.échelleY = Math.random () + 1; ;

Pour ajouter un peu plus d'ambiance à l'exemple, j'ai ajouté une texture légère qui sera sur le calque supérieur de la scène. son mélange sera réglé sur Multiplier. Les effets de particules seront désormais beaucoup plus intéressants, car leur couleur blanche de base sera modifiée pour correspondre à la lumière et la scène dans son ensemble se sentira plus intégrée..


Améliorer les performances

Un moyen courant d’optimiser la simulation de nombreuses particules consiste à utiliser le concept de mise en commun. Le regroupement vous permet de réutiliser des objets déjà créés mais dont vous n'avez plus besoin.

Le concept est simple: lorsque nous avons fini avec un certain objet, nous le plaçons dans un "pool"; ensuite, lorsque nous avons besoin d'un autre objet du même type, nous vérifions d'abord si un "en réserve" est présent dans le pool. Si c'est le cas, nous le prenons et lui appliquons de nouvelles valeurs. Nous pouvons insérer un certain nombre de ces objets dans le pool au début de la simulation pour les préparer à une utilisation ultérieure..

Pointe: Vous trouverez des informations plus détaillées sur la mise en commun dans cet article..

Une autre façon d'optimiser les effets de particules consiste à les pré-calculer en une texture. En faisant cela, vous perdrez beaucoup de flexibilité, mais l'avantage est que dessiner un effet serait la même chose que dessiner une seule image. Vous animeriez l'effet de la même manière qu'une animation de feuille de sprite normale.


Effet de particules de feu sous forme de feuille de sprite

Toutefois, vous devez faire attention: ceci n'est pas bien adapté aux effets en plein écran comme la neige, car ils prendraient beaucoup de mémoire..

Un moyen moins coûteux de simuler la neige serait d’utiliser une texture avec plusieurs flocons à l’intérieur, puis de faire une simulation similaire à celle que nous avons faite, mais en utilisant beaucoup moins de particules. Cela peut sembler bon, mais demande des efforts supplémentaires.

Voici un exemple de cela (tiré de la scène d'introduction de Fahrenheit, alias Indigo Prophecy):


Dernières pensées

Avant de commencer à écrire votre propre moteur de particules, vous devez vérifier si la technologie que vous utilisez pour rendre votre jeu déjà doté d’effets de particules ou s’il existe une bibliothèque tierce. Néanmoins, il est très utile de savoir comment elles sont implémentées et, une fois que vous comprenez bien cela, vous ne devriez pas avoir de difficulté à utiliser une variante particulière, car elles sont implémentées de la même manière. Les moteurs de particules peuvent même être livrés avec des éditeurs offrant un moyen WYSIWYG d’éditer leurs propriétés..

Si l'effet dont vous avez besoin peut être extrait dans un effet de particule basé sur une feuille de sprite, je recommanderais TimelineFX. Il peut être utilisé pour créer rapidement des effets époustouflants et dispose d'une grande bibliothèque d'effets que vous pouvez utiliser et modifier. Malheureusement, ce n'est pas l'outil le plus intuitif, et n'a pas été mis à jour depuis un moment.