Callbacks
Cette section traite de la collecte de données, de la construction de fonctions d’effets, et de l’utilisation de la classe Meter pour déclencher ces fonctions. L’accent ne sera pas mis sur l’explication des animations ou des spécificités de nos gestionnaires d’effets, donc si vous avez besoin de vous rafraîchir la mémoire, je vous recommande de consulter nos sections Tutoriels ou Référence API.
Le processus pour déclencher efficacement une animation du début à la fin est :
- Utiliser la méthode Meter.setValue(value) à l’intérieur de la fonction update pour insérer des données brutes de compteur dans une instance de votre classe Meter.
- Si le Meter se juge stable, il activera la fonction de callback passée en second paramètre.
- Cette fonction de callback contient une logique conditionnelle pour évaluer l’état du Meter. Si l’état est vrai, votre effet devrait être ajouté au tableau d’effets (effects.push(new yourEffect())) ou au gestionnaire d’état (steMgr.Push(new yourEffect())).
- Chaque exécution de la fonction update évalue le tableau d’effets complet et le dernier élément du gestionnaire d’état. À ce stade, les effets sont dessinés en fonction des variables d’état.
- Après avoir été dessinés, chaque effet contribue à la vérification de sa durée de vie. Les effets dans le tableau d’effets itèrent simplement une variable de durée de vie définie à l’instance de leur classe. Lorsque la durée de vie est à 0 ou moins, l’effet est facilement supprimé avec un splice lors de l’évaluation du tableau d’effets. Les effets dans le gestionnaire d’état peuvent se supprimer eux-mêmes si nécessaire car le gestionnaire d’état fonctionne comme une pile, et la logique conditionnelle pour ces effets tend à être plus complexe.
Le compteur
Section intitulée « Le compteur »Ce compteur cherche une barre de vie typique et retournera le pourcentage de couleur correspondante sous forme d’un nombre entre 0 et 1. La plage HSL est assez large dans ce cas pour tenir compte d’un dégradé dans la coloration de la barre. Cela introduit un problème - lorsque la barre de vie rétrécit, elle révèle en fait un fond transparent, qui pourrait inclure des éléments verts de l’environnement de jeu qui passent le contrôle de couleur et faussent notre compteur.
<head> <meta meter="health" tags="example" x= ".05" y=".9" width=".189" h="70-140" s="40-100" l="40-100" type="linear"/></head>Le Meter
Section intitulée « Le Meter »Ici, j’ai créé une instance de notre classe Meter appelée healthM pour activer un effet “dommages subis”.
<script> // Meter instance var healthM = new Meter(25, healthHelper);
function update () => { healthM.setValue(health); window.requestAnimationFrame(update); }
function healthHelper () => { // "Took Damage" effect up next }
// . . .
</script>J’ai fixé une exigence de stabilité assez élevée (25) pour tenir compte du problème d’instabilité mentionné ci-dessus. La fonction de callback attachée ne sera activée que si la valeur du compteur est stable pendant 25 mises à jour, ce qui exclura la plupart des données qui ne sont pas la barre de vie réelle, même si le joueur est immobile. Le seul inconvénient est la latence croissante de l’effet, qui sera toujours le facteur limitant dans ce scénario. Pour cette barre de vie particulière, nous pouvons contrecarrer cela de plusieurs façons :
- Lorsqu’on est touché, une partie de la barre verte est instantanément convertie en rouge avant de rétrécir jusqu’au nouveau niveau de vie vert. Si nous ajoutons un compteur healthRed cherchant du rouge au même endroit que le compteur de vie, nous pouvons combiner leurs valeurs pour un déclenchement plus rapide. Un conditionnel if ( healthM.decreased && healthRed.increased ) devrait offrir une bonne précision dans ce cas.
- Nous pouvons également combiner les lectures de plusieurs compteurs linéaires avec les mêmes paramètres de couleur. J’ai actuellement un compteur linéaire configuré au milieu de la barre de vie. Si j’en configure deux de plus, l’un légèrement au-dessus et l’autre légèrement en dessous de l’original mais toujours à l’intérieur de la barre de vie, la proximité des trois valeurs de compteur peut être utilisée comme vérification de stabilité supplémentaire. Les trois compteurs devraient tous lire Meter.decreased et être dans une petite plage les uns des autres pour déclencher l’effet.
Le Callback
Section intitulée « Le Callback »Cette fonction de callback ne s’exécute qu’après que le compteur a vérifié que tous les éléments de son tableau de valeurs sont égaux. Cela garantit que les valeurs du compteur sont basées sur des données stables. À l’intérieur du callback, j’utiliserai une instruction conditionnelle pour déterminer si l’effet doit être joué.
let healthPrev = 0;
function healthHelper () => { if (healthM.decreased && healthM.value != healthPrev){ effects.push(new healthEffect()); healthPrev = healthM.value; }}L’instruction conditionnelle nécessite deux conditions vraies : une diminution stable du compteur et une discordance entre la valeur actuelle du compteur et une valeur précédemment enregistrée. J’ai ajouté cette deuxième condition pour éviter un bug. Si nous vérifiions seulement une diminution, l’effet se jouerait de façon répétée. Puisque Meter.decreased est défini une fois que le compteur se stabilise, sa valeur reste la même tant que le compteur ne change pas. De cette façon, l’effet ne se jouera que s’il y a une diminution qui diffère de la dernière valeur stable.
Je vais créer deux effets pour vous : un pour aller dans le tableau d’effets, et un autre pour le gestionnaire d’état.
Tableau d’Effets
Section intitulée « Tableau d’Effets »Ce premier bloc de code définit une fonction d’effet destinée au tableau d’effets. Il est conçu pour être rapide et léger afin de permettre à plusieurs effets mineurs de s’exécuter simultanément. Pour réduire la charge du système, j’ai également rendu l’effet facile à personnaliser via des paramètres pour une réutilisation.
function healthEffect(color, direction, speed){ // The lifetime of the effect should be iterated in the draw function. this.lifetime = 200; this.speed = speed; this.direction = direction; this.color = color;
this.draw = () => { // Set fill from parameter ctx.fillStyle = `hsl(${this.color}, 100%, 50%)`; // Check direction if (this.direction == "up"){ ctx.fillRect(0, this.lifetime, width, 30); } else { ctx.fillRect(0, height - this.lifetime, width, 30); } // As the lifetime decreases by 5 each update, the rectangle moves accordingly. this.lifetime -= speed; } }function update() { // Background color ctx.fillStyle = "black"; ctx.fillRect(0, 0, 320, 200);
// Draw all effects in array and remove them if they are done. effects.forEach((ele, i) => { ele.draw(); if (ele.lifetime == 0){ effects.splice(i, 1); } })
window.requestAnimationFrame(update);}function healthHelper () => { if (healthM.decreased && healthM.value != healthPrev){ // Make sure to correctly set the color and direction paremeters. effects.push(new healthEffect(1, "down", 5)); healthPrev = healthM.value; }}Voici à quoi cela devrait ressembler en action :

Grâce à la conception modifiable de la fonction, nous pouvons facilement créer un effet de soin aussi ! Il suffit d’ajouter un conditionnel supplémentaire à la fonction de callback.
function healthHelper () => { if (healthM.decreased && healthM.value != healthPrev){ // Damage effect effects.push(new healthEffect(1, "down", 5)); healthPrev = healthM.value; } if (healthM.increased && healthM.value != healthPrev){ // Healing effect effects.push(new healthEffect(120, "up", 5)); healthPrev = healthM.value; }}
La simplicité de cet effet est son plus grand avantage pour un développeur LightScript. Au lieu de créer des effets de designer personnalisés, nécessitant chacun des centaines de lignes de code, construisez de petits effets réutilisables qui servent de blocs de construction pour des déclencheurs spécifiques. Les trois prochains effets sont des variations basées sur le petit effet que nous venons de créer. N’hésitez pas à expérimenter et voyez ce que vous pouvez accomplir !



Gestionnaire d’État
Section intitulée « Gestionnaire d’État »Je réserve personnellement le gestionnaire d’état pour les effets larges et complexes avec une logique d’état très spécifique, surtout lorsque vous souhaitez que l’effet ait la priorité sur tous les autres. Gardez à l’esprit que le gestionnaire d’état fonctionne comme une pile et n’exécute que l’effet du dessus ; chaque effet d’état est responsable de se supprimer lui-même quand il est terminé. Cet exemple illustre une animation de lobby de matchmaking qui s’adapte selon que vous cherchez une partie ou non. Il comprend également son propre petit tableau d’effets avec un effet auxiliaire. Les compteurs utilisés ici sont inLobby et yellowPlayButton, qui ne s’activent que dans le lobby de matchmaking. Le yellowPlayButton change de couleur selon que vous êtes en train de chercher une partie.
function lobbyAnimation(){ // Instance variables this.start = new Date().getTime(); this.elapsed = 0; this.radius = 0; this.searching = 1000; // Instance effects array this.effects = [];
// Process is the method that allows a state effect to remove itself this.Process = function () { // If the lobby menu and play button disappear, remove the effect if (inLobby.decreased && yellowPlayButton.decreased) { stateMgr.Pop(); // Global boolean to prevent duplicate lobby effects in the state handler lobbyAnim = false; } // If the play button is present, edit this.searching based on its color yellowPlayButton.value == 1 ? this.searching = 1000 : this.searching = 100; // Call the Draw function attached to the effect this.Draw(); };
this.Draw = function(){ // Iterate important variables this.elapsed = new Date().getTime() - this.start; this.radius = 50 + (Math.sin(this.elapsed/this.searching)*10);
// Fill the background to overwrite other effects ctx.fillStyle = 'black'; ctx.fillRect(0, 0, 320, 200);
// Draw the main arc ctx.beginPath(); ctx.strokeStyle = `hsl(120, 100%, 50%)`; ctx.lineWidth = 20; ctx.arc(160, 100, this.radius, 0, 2 * Math.PI); ctx.stroke(); // If the main arc is the right size, push helper effects into the helper array if(this.radius > 59 && this.elapsed % 2 == 0){ this.effects.push(new lobbyHelper(160, 100, 69)); } // Play and remove helper effects if necessary this.effects.forEach((e, i) => { ctx.lineWidth = 1; e.draw(); if (e.radius > 360) { this.effects.splice(i, 1); } }); } }
function lobbyHelper(x, y, radius){ this.x = x; this.y = y; this.radius = radius; this.draw = function(){ // Set the global transparency value. 1 is opaque, 0 is invisible. ctx.globalAlpha = 1 - this.radius/360; ctx.beginPath(); ctx.strokeStyle = `hsl(120, 100%, ${50 + (this.radius/7)}%)`; ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI); ctx.stroke(); // Set the global transparency back to 1 so your other effects are opaque ctx.globalAlpha = 1; this.radius++; }}// Declare state handler (found in Snippets)var stateMgr = new StateHandler();// Global boolean to prevent duplicate lobby animationsvar lobbyAnim = false;
function update(){ // Run the Process function to draw the animation stateMgr.Process();}function lobbyAnimationPlay(){ if(inLobby.value == 1 && yellowPlayButton.value == 1 && !lobbyAnim){ stateMgr.Push(new lobbyAnimation()); lobbyAnim = true; }};Pas en recherche :

En recherche :

Cela conclut notre tutoriel sur les callbacks, ainsi que le parcours officiel de développement SignalRGB. Restez à l’écoute pour le prochain et dernier ajout à notre parcours, Maintenance Lightscript !