SpriteEmitter
Introduction aux Emitters
Les Emitters sont des actors contenant un ou plusieurs systèmes de particules. Ils permettent de simuler de nombreux effets dynamiques tels que des flammes, des chutes d'eau, etc. UT2004 en possède 5 types différents ayant tous un emploi spécifique : SpriteEmitter, TrailEmitter, MeshEmitter, SparkEmitter et BeamEmitter. Ils peuvent être combinés dans un même actor Emitter pour réaliser des effets complexes, par exemple une flamme accompagnée de fumée et d'étincelles, une chute d'eau environnée de nuages de vapeur, des effets magiques multicolores, etc.
Le SpriteEmitter est le système de particules le plus versatile et le plus utilisé. Les autres sont en quelque sorte des dérivés spécialisés ou simplifiés qui visent à réaliser des effets précis pour lesquels le fonctionnement du SpriteEmitter ne convient pas. C'est pourquoi ce dernier sera considéré dans la suite comme le système de particules "par défaut". Néanmoins, tous ces systèmes étant proches, ils partagent les mêmes paramètres à l'exception d'un onglet spécifique à chaque type d'Emitter selon le type : Sprite pour le SpriteEmitter, Trail pour le TrailEmitter et ainsi de suite.
La liste des propriétés communes des Emitters est interminable et ne peut donc être traitée exhaustivement ici. Pour cela, il vaut mieux se référer à la référence d'UDN (en anglais) sur le sujet, qui couvre la totalité des propriétés (sauf, ironiquement, les paramètres Sprite du SpriteEmitter). Il est aussi possible, pour comprendre leur fonctionnement, d'examiner ParticleExamples, la map d'exemple fournie avec UT2004, qui contient de nombreux Emitters utilisés dans le jeu.
Afin d'illustrer les principaux paramètres, ce tutorial détaillera la réalisation de trois effets de complexité croissante : un jumper, une flamme et un effet de particules abstrait permettant d'utiliser des paramètres avancés ou rarement utilisés.
SpriteEmitter
Un SpriteEmitter émet des sprites. Il s'agit de particules rectangulaires formées de deux triangles, sur lesquelles est plaquée une texture. Ces sprites n'ont pas d'épaisseur, ne reçoivent pas de lumière, n'ont pas de collision et, par défaut, font toujours exactement face à la caméra, ce qui permet de simuler des effets volumétriques ou des étincelles.
Le SpriteEmitter doit être ajouté à un actor Emitter situé dans la map. Il se trouve relativement haut dans l'arborescence de l'actor browser et ne doit pas être confondu avec le xEmitter, situé beaucoup plus bas, qui est un système totalement différent.
L'actor a l'apparence de cinq étoiles colorées en quinconce et il est par défaut inactif.
Dans les propriétés de l'Emitter, dans l'onglet Emitter, on peut ajouter le système de particules désiré en cliquant sur le champ Emitters, ce qui fait apparaître deux boutons : "Vide" et "Ajout." Ce dernier permet d'ajouter un système de particules dont le type se choisit dans une liste déroulante.
Après avoir choisi le type de particules désiré (ici, le SpriteEmitter) et cliqué sur le bouton "Nouv.", une impressionnante liste de 26 onglets de propriétés apparaît.
Si le rendu en temps réel est activé dans la vue 3D (en cliquant sur le joystick dans l'en-tête grise du viewport), l'effet est immédiatement actif : des sprites de grande taille sont émis autour de l'actor, portant la texture par défaut (celle de l'actor lui-même). Ils sont émis en continu et s'empilent les uns sur les autres jusqu'à ce que leur superposition rende l'effet blanc.
Le SpriteEmitter est alors fonctionnel et peut être réglé comme souhaité. Toute modification d'un paramètre réinitialise l'effet avec ses nouveaux réglages, ce qui permet de constater en temps réel les changements et rend facile d'atteindre le résultat voulu.
Jumper
Un jumper est un actor invisible qu'il est nécessaire de signaler visuellement. La plupart du temps, son emplacement est marqué avec un Emitter, qui indique souvent la direction dans laquelle le joueur sera propulsé. Beaucoup de maps d'UT2004 originellement créées pour Unreal Championship ou UT2003 utilisent un visuel similaire, composé d'un static-mesh de base carrée (la wildcare base) et un Emitter en deux parties : une grille carrée qui émane de la base et des petits traits lumineux. On peut voir cet effet à l'œuvre dans BR-Skyline, DM-Plunge ou BR-Disclosure, par exemple.
Il s'agit d'un effet simple qui permet d'utiliser les principaux paramètres du SpriteEmitter : changer la taille des particules, leur donner un mouvement linéaire, modifier leur alignement et leur couleur.
Apparence
Commençons par la grille : dans l'onglet Texture de l'Emitter, le champ Texture indique la texture appliquée aux sprites et contient par défaut la référence de la texture de l'actor lui-même. Celle qui nous intéresse se trouve dans le package EpicParticles, groupe JumpPad. Après l'avoir sélectionnée dans le texture browser, il suffit de la renseigner dans ce champ pour que les particules l'utilisent.
Il est possible de changer le mode de rendu des particules grâce au paramètre DrawStyle dans ce même onglet. Le mode par défaut, PTDS_Translucent est le plus utilisé. Il affiche la texture en rendant chaque pixel plus ou moins transparent suivant sa couleur : plus il est sombre, plus son opacité baisse (le noir est totalement invisible). Les parties les plus claires sont plus opaques et brillantes.
Si l'on se déplace autour de l'Emitter, on constate que les sprites font toujours face à la caméra. Dans le cas du jumper, ils doivent être parallèles au sol et il est donc nécessaire de leur indiquer l'orientation qu'on souhaite les voir conserver quel que soit l'angle de vue. Pour ce faire, il faut choisir PTDU_Normal dans Sprite → UseDirectionAs.
Le sprite utilise alors la normale indiquée dans ProjectionNormal. Par défaut, cette normale est X = 0, Y = 0, Z = 1, ce qui signifie que le sprite est orienté face à l'axe Z (l'axe vertical), donc à l'horizontale, ce qui est l'effet recherché ici. On pourrait cependant projeter les particules à la verticale, orientées face aux axes X ou Y, en mettant 1 dans l'axe voulu et 0 dans l'axe Z. En mettant 1 dans plusieurs axes, on oriente les sprites exactement entre les deux. Des valeurs décimales permettent de n'orienter que partiellement les particules vers cet axe et donc de faire en sorte que les particules aient une orientation précise.
Les autres valeurs de UseDirectionAs permettent de donner aux sprites des orientations différentes, dépendantes de la direction de leur mouvement.
- PTDU_None : Le sprite n'a pas d'axe de projection, il fait toujours face à la caméra (c'est le mode par défaut).
- PTDU_Up : Le sprite est orienté parallèlement à sa direction, le haut de la texture pointé vers l'avant.
- PTDU_Right : Identique à PTDU_Up, mais le sprite est tourné à 90 degrés pour que la droite de la texture soit orientée vers l'avant.
- PTDU_Forward : Le sprite est projeté perpendiculairement à sa direction.
- PTDU_UpAndNormal et PTDU_RightAndNormal : Combinaison de PTDU_Normal et PTDU_Up/PTDU_Right. Le sprite est projeté parallèlement à sa direction, le haut ou la droite de la texture vers l'avant, mais il est aussi orienté vers l'axe désigné par PTDU_Normal.
- PTDU_Scale : Similaire à PTDU_None mais permet d'utiliser UniformSize = faux (détaillé plus bas).
La taille des particules ne dépend pas de celle de la texture. Elle est définie de façon absolue dans l'onglet Size par le paramètre StartSizeRange.
Comme tous les paramètres de l'Emitter dont le nom se termine par "Range", ce paramètre permet d'indiquer deux bornes, Min et Max. Si deux valeurs différentes sont indiqués, une valeur aléatoire entre ces deux bornes est choisie par le moteur du jeu à la création de chaque particule, ce qui permet ainsi d'introduire une variation aléatoire entre deux extrêmes pour le paramètre considéré. Si Min et Max sont identiques, il n'y a aucune variation et chaque particule reçoit la même valeur. Ces intervalles peuvent concerner aussi bien la vitesse que la taille ou la durée de vie de la particule.
Dans le cas de StartSizeRange, un intervalle différent peut être utilisé sur chaque axe car la taille peut différer sur les axes X, Y et Z. Les dimensions se composent donc de 6 champs : X Min et Max, Y Min et Max et Z Min et Max.
Le static-mesh de base utilisé pour le jumper est XGame_rc.AmmoChargerMesh. On peut le poser dans la map et positionner l'Emitter au centre.
En modifiant les valeurs de X, on peut faire correspondre la texture au static-mesh. Le nombre indiqué détermine la taille (en unités Unreal) entre le centre de la particule et ses bords sur l'axe indiqué. Ainsi, si X = 100 (avec Min et Max identiques), la particule mesure 200 unités sur l'axe X.
Le static-mesh utilisé comme jumper présente une plateforme centrale d'environ 64 par 50. On peut prendre un peu de marge, comme c'est le cas des maps d'UT2004 qui utilisent cet effet, et viser eviron 70 par 60. En mettant 35 dans Min et Max pour X, on obtient quelque chose de proche.
Ajouter 30 dans le Min et Max d'Y semble sans effet et la particule reste carrée. Pour pouvoir modifier chaque axe indépendament, il faut en effet désactiver UniformSize, qui est vrai par défaut, et qui force le SpriteEmitter à ignorer les valeurs de Y et Z pour n'utiliser que celles de X. Une fois ceci fait, la valeur de Y peut être changée et la particule s'étire en rectangles de toutes les tailles possibles.
Notez que comme les sprites sont des particules en deux dimensions, l'axe Z est inutilisé dans tous les cas. Il n'est utile que pour le MeshEmitter, quand les particules sont des static-meshes en 3D.
UniformSize fonctionne pour tous les systèmes de particules, mais dans le cas du SpriteEmitter, une subtilité vient s'y ajouter : si Sprite → UseDirectionAs est réglé sur sa valeur par défaut, PTDU_None, alors UniformSize devient automatiquement vrai et ne peut pas être désactivé. En effet, PTDU_None force la particule à faire toujours face à la caméra, éliminant toute référence aux axes Y et Z, qui ne peuvent donc plus être changés séparément. La particule ne peut alors qu'être mise globalement à l'échelle.
Utiliser UseDirectionAS → PTDU_Scale permet de désactiver UniformSize tout en laissant les sprites faire face à la caméra. Leur orientation reste cependant dépendante de l'angle depuis lequel ils sont vus.
La couleur des particules peut être modifiée sans toucher à la texture dans l'onglet Color.
Il permet de modifier la texture de deux façons : globalement, avec ColorMultiplierRange, ou dans le temps, avec ColorScale.
ColorMultiplierRange permet de multiplier X, Y et Z, qui correspondent respectivement à rouge, vert et bleu. Régler par exemple X = 0.5 permet de diviser par deux la quantité de rouge de chaque pixel de la texture. Ce paramètre permet donc de modifier la couleur de la texture de façon uniforme, mais aussi de donner une variation aléatoire à chaque couleur de chaque particule en utilisant un Max et un Min différents.
ColorScale permet de faire varier la couleur de la texture tout au long de sa durée de vie. Il s'agit d'une liste à laquelle des éléments peuvent être ajoutés, chacun composé d'une couleur et d'un RelativeTime.
RelativeTime doit être compris entre 0 et 1. Quelle que soit la durée de vie de la particule, 0 correspond au moment de sa naissance et 1 au moment de sa mort. 0.5 correspond ainsi à la moitié de sa durée de vie, que la particule existe pendant 2 ou 37 secondes. Color détermine la couleur de la particule au moment précis où RelativeTime est atteint. Entre deux éléments, la particule fait une transition progressive entre les deux couleurs de départ et d'arrivée.
Le ColorScale nécessite au moins deux éléments avec des RelativeTimes différents pour fonctionner. De plus, UseColorScale doit être réglé sur vrai. la couleur peut être choisie en insérant à la main des valeurs RGB (un champ A existe pour le canal alpha, mais il est sans effet) ou prise sur l'écran ou dans un nuancier en cliquant sur les boutons "Ramas." ou "..." qui apparaissent lorsqu'un des éléments du ColorScale est ouvert.
Le ColorScale peut contenir autant d'éléments que désiré pour que la particule passe par de nombreuses couleurs au cours de sa vie. Si l'on veut créer un cycle de couleur qui se répète (par exemple rouge, puis vert, puis rouge, puis encore vert), il est possible de ne créer que le premier cycle et d'utiliser ColorScaleRepeats. Ce dernier est par défaut à 0, ce qui signifie que le cycle n'est pas répété. S'il est réglé sur 1, le cycle est répété une fois, ce qui signifie que le ColorScale est joué deux fois au cours de la vie de la particule (le RelativeTime de 1 devient donc non pas la fin de la vie de la particule, mais sa moitié). Attention cependant : lorsque le ColorScale entier a été joué et que la répétition commence, la particule passe brutalement de la dernière à la première couleur, sans transition. Il est donc préférable que le ColorScale commence et se termine par la même couleur pour éviter un changement abrupt.
Si une partie de la vie de la particule n'est pas couverte par le ColorScale, la couleur par défaut de la texture est utilisée pendant cette période.
Le dernier paramètre de l'onglet, Opacity, est un multiplicateur d'opacité de la texture. 1 est le maximum et la valeur par défaut, 0 correspond à une texture totalement transparente. 0.5 correspond donc à une opacité de 50 %.
Dans notre exemple, nous utiliserons un ColorScale passant du jaune au rouge. Il faut donc régler UseColorScale sur vrai et ajouter deux éléments :
Malheureusement, les particules s'empilant les unes sur les autres, les couleurs se multiplient et la variation du ColorScale finit par être invisible. Pour que les particules ne partagent plus la même position, il est nécessaire de les mettre en mouvement.
Accélération
Les grilles du jumper se déplacent vers le haut. Avant de reproduire ce mouvement, il est important de comprendre qu'il existe deux façons de déplacer les particules : à vitesse constante, avec les paramètres de l'onglet Velocity, ou avec une accélération, c'est-à-dire une augmentation constante de la vitesse, dans l'onglet Acceleration (ces deux types de mouvements peuvent être combinés).
L'onglet Acceleration est le plus simple car il ne comporte que trois paramètres : une valeur par axe. Plus une valeur est haute, plus la particule accélère sur cet axe. Si la valeur est négative, la particule se déplace dans le sens inverse de l'axe. La particule est immobile lors de sa création et prend de la vitesse tout au long de sa vie.
Dans le cas de la grille du jumper, elle s'élève verticalement, ce qui signifie qu'il est nécessaire d'indiquer une valeur positive dans Z. Une valeur de 50 permet d'avoir un mouvement relativement lent, l'accélération étant modérée.
Finalisation
Les dernières touches à apporter sont de raccourcir la durée de vie des particules, qui restent visibles 4 secondes par défaut, et de modifier leur nombre, qui est de 10 par défaut.
La durée de vie se règle dans Time → LifeTimeRange. Par défaut Min et Max sont à 4 et toutes les particules vivent donc 4 secondes. Pour garder l'effet près du sol au lieu d'avoir une colonne de particules, on peut mettre les deux valeurs à 1.
Enfin, le nombre de particules est trop élevé pour l'effet tel qu'il est désormais réglé et les grilles s'enchaînent très vite, avec peu d'écart. Le nombre maximum de particules que le SpriteEmitter maintient en vie à un moment donné peut être modifié dans General → MaxParticles. Passer de 10 à 5 donne un meilleur effet.
Pour terminer, on peut ajouter un effet de "fondu" aux particules afin qu'elles ne disparaissent pas abrutement lorsque leur vie arrive à son terme. Pour cela, il faut utiliser l'onglet Fading :
Cet onglet permet de paramétrer le "FadeIn" et le "FadeOut". FadeIn permet de faire apparaître les particules en douceur : elles spawnent invisibles et gagnent en opacité progressivement. FadeOut permet l'inverse : à partir d'un moment donné, la particule devient progressivement transparente et finit par devenir invisible.
Seul le FadeOut nous intéresse, il faut donc régler FadeOut = vrai. FadeOutStartTime permet de donner le délai (en secondes) entre la naissance de la particule et le début du fondu (s'il est laissé à 0, le fondu commence dès le spawn de la particule). 0.5 permet de réaliser le fondu sur la seconde moitié la vie de la particule.
Pour un FadeIn, le fonctionnement est le même mais en symétrique : FadeInEndTime indique après combien de secondes de vie la particule atteint son opacité maximale.
FadeInFactor et FadeOutFactor sont rarement utilisés mais permettent d'appliquer le fondu à certaines couleurs uniquement (W = alpha, X = rouge, Y = vert et B = bleu).
L'effet est assez convaincant et ressemble assez à l'effet vu dans les maps telles que BR-Skyline (qui utilise cependant une durée de vie un peu plus longue pour ses particules). Pour compléter cet effet, on peut cependant ajouter un second SpriteEmitter à notre Emitter pour générer les petits traits colorés qui accompagnent les grilles.
En retournant au sommet de l'onglet Emitter, on peut ajouter un second SpriteEmitter à l'actor. Son comportement n'est pas très différent du premier, mais les traits sont beaucoup plus petits et doivent donc être spawnés sur toute la surface du jumper et non seulement en son centre.
Les particules utilisent la texture EpicParticles.Beams.Streak02aw et se déplacent comme les grilles, sur l'axe Z, quoiqu'un peu plus vite (accélération de 80 sur Z). Dans l'onglet Sprite, UseDirectionAs est réglé sur PTDU_Up, ce qui permet d'aligner les textures parallèlement à leur axe de déplacement (ici Z). Elles sont dont bien alignés sur l'axe vertical. Leur durée de vie est la même que celle des grilles.
Leur taille est de X = 6, Y = 4. UniformSize peut en effet être désactivé du fait que UseDirectionAs n'est pas PTDU_None. Une fois que les particules ont reçu le même ColorScale et le même FadeOut que les grilles, le seul problème est que les traits ne sont émis que depuis la position exacte de l'actor Emitter. Pour les faire apparaître sur une zone définie, il faut utiliser l'onglet Location.
Le paramètre le plus important est StartLocationRange qui permet de définir un cube dans lequel les particules sont générées. Pour le jumper, la zone de spawn est un plan, donc un cube sans épaisseur. Z doit ainsi rester 0. Les valeurs de X et Y permettent de définir un rectangle dans lequel sont générés les traits, par rapport à la position de l'Emitter. Comme vu plus haut, la plateforme centrale du jumper mesure environ 64 par 50 unités. On peut donc donner pour l'axe X Max = 30 et Min = -30, ce qui fait un écart de 60. Pour l'axe Y, Max = 24 et Min = -24 donnent un écart de 48. Paramétrer l'écart autour de 0 permet de centrer la zone de spawn sur l'Emitter.
En s'assurant que StartLocationRange est légèrement plus petit que la taille réelle du static-mesh, on s'assure que les particules sont bien générées dans la plateforme. Si la zone correspondait exactement, des particules pourraient être spawnées à moitié dans la base, ce qui serait inesthétique.
Si toutes les valeurs sont positives (ou négatives), il est possible de positionner la zone de spawn par rapport à l'Emitter, mais à distance de lui.
StartLocationOffest peut cependant être utilisé pour décaler la zone de spawn déjà paramétrée à distance de l'Emitter sans avoir à modifier directement les valeurs de StartLocationRange. Ce paramètre peut s'avérer utile lorsqu'un même Emitter gère plusieurs systèmes de particules et que, suite à une modification, tout l'effet doit rester identique mais être légèrement déplacé.
Par défaut, seuls StartLocationRange et StartLocationoffset sont pris en compte car StartLocationShape est réglé sur PTLS_Box. Les autres paramètres de l'onglet Location permettent de spawner les particules dans le volume d'une sphère ou, plus exotique, sur la surface d'une sphère, mais ces positions nécessitent que StartLocationShape soit paramétré différemment.
L'effet du jumper est maintenant terminé, mais il est facile de le compléter pour l'adapter à des situations spécifiques. Par exemple, dans un mode de jeu par équipes, il peut être copié/collé d'une base à l'autre et modifié seulement au niveau du ColorScale pour que les particules soient rouges d'un côté de la map et bleues de l'autre. Si différents jumpers propulsent les joueurs à différentes hauteurs, la vitesse ou la durée de vie des particules peuvent être augmentées pour que la colonne de particules soit plus haute, indiquant un mouvement plus grand. Un mouvement latéral peut aussi être ajouté au mouvement vertical afin d'indiquer la direction dans laquelle le joueur sera envoyé, en ajoutant des accélérations sur les axes X ou Y.
Flamme
Le feu étant en mouvement constant, il est difficile de le simuler avec des sprites portant une seule texture. Ce n'est pas impossible : Unreal 2, par exemple, utilise des sprites avec une orientation aléatoire, en rotation rapide, avec une grande vitesse et une durée de vie très courte pour masquer la répétition de la même image. Le SpriteEmitter propose une technique plus facile : l'utilisation de textures subdivisées pour jouer une animation ou pour générer des particules différentes. De telles textures peuvent être trouvées dans le package EmitterTextures, dans le groupe MultiFrame.
Après avoir ajouté un SpriteEmitter à un Emitter et avoir configuré la texture, on peut indiquer combien de subdivisions composent la texture dans Texture → TextureUSubdivisions et TextureVSubdivisions, pour indiquer respectivement le nombre de "cases" sur les axes horizontaux et verticaux de la texture.
Chaque particule créée reçoit alors la première subdivision et passe par toutes les "cases" de la texture au fur et à mesure de son existence. Plus la vie de la particule est courte, plus le défilement des subdivisons est rapide car le système s'arrange pour que toutes soient utilisées. On le constate en donnant un mouvement vertical aux sprites avec une accélération sur Z (il vaut mieux diminuer leur taille également).
Le changement d'une case à l'autre de la texture peut être adouci avec BlendBetweenSubdivisons qui opère un fondu d'une subdivison à l'autre.
Les subdivisions sont utilisées dans un ordre précis : la particule commence par celle tout en haut à gauche de la texture puis passe à celle du dessous et ainsi de suite. Une fois le bas atteint, la particule passe à la colonne suivante et la descend à son tour.
La texture EmitterTextures.FrameAnim.anim1pcl est ainsi faussée : elle laisse supposer que le défilement se fait ligne à ligne, mais il suffit de l'utiliser pour voir que le défilement est vertical.
Bien qu'elles ne soient pas utiles dans le cadre de notre flamme, les propriétés restantes de l'onglet Texture permettent d'agir sur les textures subdivisées. SubdivisonEnd et SubdivisionStart permettent de choisir sur quelles cases commence et termine le défilement. Attention : le défilement ne peut pas "boucler" : une fois la dernière subdivison atteinte, la particule ne recommence pas à la première et reste figée sur la dernière case. Si SubdivisionEnd est plus élevé que SubdivisionStart, l'Emitter ne comprend pas comment faire défiler les images dans le sens inverse et seule la case indiquée dans SubdivisionStart est appliquée fixement.
UseSubdivisionScale s'utilise avec la liste SubdivisionScale. Il s'agit d'un moyen de contrôler le temps d'affichage de chaque subdivision, de façon un peu exotique. Une fois que UsSubdivisionScale = vrai, chaque élément numéroté ajouté à SubdivisionScale correspond à une case de la texture. Chacun possède un paramètre chiffré compris entre 0 et 1, qui correspond non pas au temps d'affichage, mais au pourcentage de la vie de la texture jusqu'auquel la subdivision correspondante est appliquée. Chaque élément doit donc avoir une valeur supérieure au précédent sous peine de pas fonctionner (l'Emitter ne peut pas remonter le temps).
Ce fonctionnement un peu compliqué permet par exemple, en indiquant 0.5 dans l'élément noté [0], de s'assurer que la première subdivision reste affichée sur la particule pendant 50 % de son existence, puis passe à la seconde. Pour l'élément [1], qui correspond à la cette seconde subdivision, indiquer 0.75 fait en sorte que la particule passe à la troisième subdivision aux trois quarts de la vie de la particule. Si aucun autre élément n'est ajouté au SubdivisionScale, la particule passe ainsi par seulement trois subdivisions, qui changent aux moments voulus. Il n'est en revanche pas possible de changer l'ordre de passage des subdivisions.
Notez que la première subdivision est numérotée 0. Pour une texture en 16 subdivisions comme la nôtre, il faut donc compter de 0 à 16 et non de 1 à 16.
UseRandomSubdivisions assigne à chaque particule une subdivision aléatoire et fixe : chaque sprite n'a qu'une seule image qui ne change pas. Si ce paramètre est vrai, le SubdivisionScale est ignoré s'il est activé, mais SubdivisionStart et SubdivisionEnd sont pris en compte : seules les subdivisions prises en compte sont utilisées pour déterminer l'apparence aléatoire des sprites.
Pour en revenir à notre flamme, après avoir mis TextureUSubdivisions et TextureVSubdivisions à 4 et UseRandomSubdivisions = vrai, il faut mettre les particules à la bonne taille (20 dans mon cas) et baisser la durée de vie. Pour une flamme modeste, j'ai choisi de la ramener à 2 secondes.
Il faut maintenant affiner le mouvement. Pour une flamme, il est préférable d'avoir une vitesse verticale constante plutôt qu'une accélération. Cette dernière étant donc remise à zéro, on peut régler une vitesse fixe dans Velocity → StartVelocityRange. Contrairement à l'accélération, les paramètres de Velocity acceptent un minimum et un maximum.
La flamme ne s'élève que depuis le centre de l'Emitter. Pour un feu plus imposant qu'une simple torche, on peut lui assigner un StartLocationRange.
Avec un FadeIn, un FadeOut et un ColorScale allant du jaune au rouge, on obtient déjà un effet convaincant (le nombre de particules a également été augmenté à 15 dans General → MaxParticles).
Un inconvénient, cependant : la texture est composée de flammes inclinées à 45 degrés et il est préférable de les aligner vers le haut. Pour cela, il faut régler la rotation de départ des particules dans l'onglet Rotation. La paramètre SpinParticles doit être vrai pour pouvoir utiliser les options de cet onglet et il devient alors possible de faire tourner chaque sprite sur lui-même avec StartSpinRange. Les valeurs indiquées sont des angles en unités Unreal où 65536 = 360°. Pour faire un huitième de tour, il faut donc utiliser la valeur 8192. Seules les valeurs indiquées pour X sont prises en compte pour le SpriteEmitter.
Pour finaliser l'effet, on peut décider de donner un mouvement radial aux particules pour que la flamme ne ressemble pas à une colonne carrée mais à une sorte de cône. Le mouvement radial ramène les particules en direction de l'Emitter.
Dans l'onglet Velocity, il faut régler GetVelocityDirectionFrom sur PTDV_AddRadial pour que ce mouvement soit ajouté à tout autre mouvement affectant les particules, puis mettre une valeur positive dans StartVelocityRange, qui correspondra à la force d'attraction de l'Emitter sur les particules.
Le mouvement radial est calculé une fois à la création de la particule. Si elle se déplace déjà en direction de l'Emitter, elle sera accélérée d'autant. Si au contraire, elle s'en éloigne, son mouvement sera donc ralenti.
Il ne faut pas donner une valeur trop haute à StartVelocityRadialRange car les particules ne s'immobilisent pas au centre : si les valeurs sont trop fortes, elles continuent leur chemin et la flamme aura la forme d'un sablier.
Si la vitesse radiale est négative, la direction du mouvement radial est inversée et les particules tendant au contraire à s'éloigner de l'actor.
La flamme est terminée. Il est possible d'améliorer encore l'effet de multiples façons et bien sûr d'ajouter un SpriteEmitter supplémentaire à l'Emitter pour générer de la fumée. Celle-ci peut être générée dans la flamme elle-même mais avec un très long FadeIn pour que la flamme se fonde dans le nuage noir qui se gagne en opacité en se détachant du feu. Les particules de fumée peuvent aussi recevoir une accélération latérale pour donner l'impression que le vent la pousse.
La texture de fumée utilise DrawStyle → PTDS_Darken pour apparaître très sombre.
On peut également utiliser un SparkEmitter à la base de la flamme pour imiter le craquement des braises si l'Emitter est placée sur un foyer ou dans un brasier.
Effets liés
Ce dernier exemple permettra de mettre en rapport direct plusieurs SpriteEmitters. Jusqu'ici, les différents systèmes de particules coexistaient de manière indépendante dans le même Emitter mais n'avaient aucun lien : la grille du jumper et les traits générés pouvaient être déplacés séparément, la fumée pouvait être déplacée loin du feu, etc. Cette troisièmé démonstration utilisera des SpriteEmitters dont la position et le mouvement seront déterminés par un autre.
Cet effet abstrait sera constitué de deux SpriteEmitters : un premier pour gérer des particules en rotation constante, propulsées par l'Emitter dans un cône orienté vers un mur, et un second générant des particules plus petites lorsque les premières entrent en collision avec le niveau.
SpriteEmitter principal
Le premier effet est relativement simple : le premier SpriteEmitter ajouté dans notre Emitter émet des particules de taille modeste. La texture utilisée est EpicParticles.Flares.Rev_Particle1.
Le StartVelocityRange est configuré avec une unique vitesse sur l'axe X. Mais pour Y et Z, la même valeur est utilisée en positif dans Max et en négatif dans Min (par exemple Max = 35, Min = -35). Ainsi, les particules forment un cône centré sur l'axe X.
La durée de vie des sprites doit être suffisante pour que les particules touchent les murs de la map et passent à travers.
Comme la texture présente deux petites étoiles, mettre les particules en rotation permet de donner l'impression que la plus petite, qui est excentré, tourne autour de la plus grosse. Dans l'onglet Rotation, il faut donc utiliser non pas StartSpinRange, qui définit l'orientation de départ, mais SpinsPerSecondRange, qui permet d'appliquer une rotation continue. La valeur spécifiée indique le nombre de tours par seconde. Pour nos sprites en deux dimensions, seul le champ X est pris en compte, les autres ne servant que pour le MeshEmitter.
Le sens de rotation des particules est défini par SpinCCWorCW. Ce paramètre possède un champ pour chaque axe, mais, de nouveau, seul X est utile pour les SpriteEmitters. Si X = 0, toutes les particules tournent sur elles-mêmes dans le sens des aiguilles d'une montre. Si X = 1, elles tournent toutes dans le sens inverse. Si une valeur intermédiaire est utilisée, elle correspond au pourcentage de chances de tourner dans le sens anti-horaire : 0.7 correspond ainsi à 70 % de chances. Par défaut, tous les axes sont réglés sur 0.5 ce qui signifie que toutes les particules ont 50 % de chances de tourner dans un sens ou dans l'autre. Dans notre cas, une vitesse de 1 (Min et Max sur X) et un sens de rotation aléatoire (SpinCCWorCC = 0.5) suffisent pour notre effet.
Il n'y a plus qu'à faire en sorte que les particules soient détruites au contact de la géométrie. Pour cela, il suffit de se rendre dans l'onglet Collision, qui gère les interactions avec le niveau.
UseCollision permet d'activer la prise en compte de la géométrie par les particules. Cela permet par défaut de les faire rebondir sur les polygones avec lesquels elles rentent en contact. En réglant UseMaxCollisions à vrai, il est possible d'indiquer un nombre maximum de contacts que la particule accepte avant d'être détruite. Ce nombre se règle dans MaxCollisions. 0 ou 1 indiquent de détruire la particule au moindre contact.
Pour notre exemple, ce système est pour l'instant suffisant. La prochaine étape est de générer des particules à l'endroit exact où celles du SpriteEmitter principal sont détruites.
SpriteEmitter secondaire
Un SpriteEmitter peut être invoqué par un autre pour réaliser, par exemple, des effets d'impact. Après avoir ajouté un second système de particules à l'Emitter, configurez-le comme souhaité, centré sur sa position par défaut (l'actor lui-même). Dans cet exemple, il s'agit de particules qui jaillissent dans tous les sens (avec en plus une accélération négative sur Z pour simuler la gravité), avec un ColorScale et une durée de vie baissée à 2.
Une série de paramètres permet ensuite de lier ensemble les deux SpriteEmitters qui sont pour l'instant totalement dissociés. Pour cela, il faut retourner dans le SpriteEmitter principal (le "parent") et régler Collision → SpawnFromOtherEmitter = 1. Ce paramètre permet de spécifier quel autre système de particules invoquer lorsqu'une particule entre en collision avec la géométrie. Le SpriteEmitter principal étant numéroté 0, le suivant est le 1.
Par défaut, cela n'entraîne aucun effet particulier. En réglant SpawnAmount sur une valeur supérieure à 0 dans le même onglet, on définit combien de particules du SpriteEmitter secondaire générer lors d'une collision. Dans notre cas, 10 est une valeur adéquate.
Un mot sur le fonctionnement de cet ensemble de propriétés : le système secondaire gère son propre nombre de particules (General → MaxParticles). Si le nombre est trop bas, lorsque le SpriteEmitter parent ordonner de générer des particules, il est possible que le nombre de sprites en jeu dépasse la valeur de MaxParticles du SpriteEmitter secondaire. Les particules les plus anciennes sont alors abruptement supprimées pour que de nouvelles puissent être spawnées. Il faut doc augmenter assez le MaxParticles du système secondaire pour qu'il ait toujours des particules à spawner en surplus.
À l'inverse, si le MaxParticles du système secondaire est trop élevé, les particules en trop sont spawnées à leur emplacement par défaut : l'Emitter lui-même.
Pour empêcher cela, il faut régler Spawning → AutomaticInitialSpawning et Local → RespawnDeadParticles sur faux. Le premier paramètre désactive le système de particule concerné tant qu'il n'est pas déclenché extérieurement (par exemple en étant invoqué par le système parent), ce qui empêche que l'effet ne commence autour de l'Emitter avant même la première collision. Le second empêche le SpriteEmitter de spawner infiniment ses particules.
En effet, si RespawnDeadParticles est laissé sur vrai, les particules spawnées lors d'une collision sont régénérées une fois leur durée de vie écoulée. Comme elles n'ont plus de particule parente au niveau de laquelle spawner, elles sont régénérées sur leur position par défaut : l'actor Emitter. Au fur et à mesure que des collisions activent des particules (par bouffées réglées par SpawnAmount), le nombre de particules spawnées sur l'Emitter augmente jusqu'à reproduire le problème initial : le SpriteEmitter maintient en vie autant de particules que spécifié par MaxParticles, celles n'étant pas liées à des particules du SpriteEmitter parent étant accumulées autour de l'actor lui-même.
Avec AutomaticInitialSpawning et RespawnDeadParticles tous les deux désactivés, la valeur de MaxParticles n'a pas d'importance tant qu'elle est assez élevée pour que le système ne soit jamais acculé à supprimer des particules encore en jeu pour en spawner de nouvelles. Dans notre cas, le SpriteEmitter principal a un MaxParticles de 10 et chaque collision crée 10 particules filles, ce qui signifie que le nombre de particules du second SpriteEmitter à un instant T devrait osciller autour de 100. Une valeur de 150 permet d'avoir de la marge. Une fois le système réglé ainsi, les particules ne sont spawnées que lors des impacts.
Pour bien faire, on ajoute un FadeOut aux particules filles et on peut activer leur paramètre UseCollision pour qu'elles réagissent aux contacts également. Si leur durée de vie est assez haute, n'ayant pas de MaxCollisions, elles rebondissent sur le sol et les murs jusqu'à arriver au terme de leur durée de vie. La force de ces rebonds peut être ajustée avec DampingFactorRange.
Ce paramètre permet de définir quel pourcentage de leur vitesse les particules conservent sur chaque axe lors d'une collision. Par défaut, toutes les valeurs sont à 1, ce qui signifie qu'après un contact, la particule change de direction mais conserve sa vitesse.
Baisser la valeur sur l'axe Z, par exemple, permet de faire perdre beaucoup de vitesse verticale lors d'un rebond, ce qui, couplé à l'accélération négative sur Z, permet de simuler la gravité. Avec des valeurs proches de 1, les particules se comportent comme de véritables balles rebondissantes.
Il est aussi possible de donner des valeurs supérieures à 1, ce qui augmente la vitesse des particules à chaque rebond, pour un effet anarchique. Dans notre cas, diminuer la vitesse sur Z à Max = 0.3 et Min = 0.1 permet de donner un effet crédible de gravité.
L'effet visuel est maintenant terminé. Chaque particule blanche en rotation est projetée en direction du mur où elle est détruite au contact de la géométrie, propulsant des étincelles de couleur qui tombent au sol et s'y immobilisent.
Effets sonores
On peut compléter l'effet précédent avec des bruitages. Le système permet de jouer des sons à deux moments précis : lors du spawn de la particule et lorsqu'elle entre en collision avec la géométrie. Tout se règle dans l'onglet Sound du SpriteEmitter et le système est relativement intuitif :
Sounds permet d'ajouter une liste de sons à disposition du SpriteEmitter. Chaque son est numéroté (c'est important) et peut être réglé individuellement :
- Pitch : À quel pitch le son est joué. 1 correspond à son pitch normal.
- Probability : Pourcentage de chance que le son soit joué lorsqu'il est demandé. Il est toujours joué sur demande si la valeur est 1 et jamais si la valeur est 0.
- Radius : Distance (abstraite) à laquelle le son est audible.
- Sound : Indique le son sélectionné. Il peut être choisi dans le sound browser.
- Volume : Volume relatif du son. 1 correspond à son plein volume et cette propriété n'accepte que des valeurs entre 0 et 1. Le volume ne peut donc qu'être diminué.
- Weight : Paramètre obsolète sans effet.
Par défaut, toutes les valeurs étant à 0, aucun son ajouté n'est audible. Ajouter 1 pour toutes les valuers de Pitch, Probability et Volume et une valeur comme 64 dans Radius permet d'activer le bruitage. Il ne reste alors plus qu'à l'appeler.
Deux ensembles de paramètres gèrent la façon dont les sons sont joués lors des collisions ou du spawn :
- CollisionSound et SpawningSound : Comment les sons sont sélectionés.
- PTSC_None : Aucun son n'est utilisé. Il s'agit du mode par défaut, qui doit donc forcément être changé pour que les bruitages soient effectivement activés sur le système de particules.
- PTSC_LinearGlobal : La liste des sons est partagée par toutes les particules du SpriteEmitter et lue linéairement à chaque événement. Utilisé avec SpawningSound, ce mode permet de jouer le premier son lors du spawn de la première particule, le second lorsque la deuxième particule apparaît, etc. Utilisé avec CollisionSound, chaque collision passe au son suivant, quelle que soit la particule qui entre en contact avec la géométrie.
- PTSC_LinearLocal : Non fonctionnel pour SpawningSound (le résultat est identique à PTSC_LinearGlobal). Si ce mode est choisi pour CollisionSound, chaque particule parcourt individuellement la liste des sons : elle joue le premier son lors de son premier contact avec la géométrie, le deuxième son à sa seconde collision, etc.
- PTSC_Random : Un son est choisi au hasard à chaque événement.
- CollisionSoundIndex et SpawningSoundIndex : Ces propriétés permettent d'indiquer quels sons sont utilisables par l'Emitter lors de la collision ou du spawn en indiquant leurs positions dans la liste Sounds. Si Min et Max sont tous les deux 0, la liste entière est utilisée. Si 5 sons sont ajoutés mais que les deux premiers ne doivent être utilisés que pour les spawns et les autres pour les collisions, par exemple, il faut indiquer SpawningSoundIndex Min = 0, Max = 1, et CollisionSoundIndex Min = 2, Max = 4.
- CollisionSoundProbability et SpawningSoundProbability : Probabilité que l'Emitter décide de jouer un son lors de l'événement. Il y a donc un double niveau de probabilité possible : ce paramètre permet de déterminer aléatoirement si un son peut être joué, et le paramètre Probability du son dans la liste Sounds définit les chances que le son soit effectivement joué.
Un exemple d'utilisation des sons se trouve dans BR-Riveting, où les balises lumineuses indiquant le chemin ont un effet clignotant : une grande particule rotative est très brièvement spawnée à intervalle régulier. Ces particules émettent un son lors de leur spawn, ce qui permet de faire bipper les balises de façon parfaitement synchronisée avec l'effet visuel.
RSS Feed