Utiliser des timers pour programmer et répéter des actions, ou instaurer des délais

Les timers constituent une fonctionnalité de Phaser particulièrement utile pour différentes situations, comme par exemple :

  • Programmer ou délayer l’exécution d’une action : par exemple, votre personnage vient de mourir, et vous avez affiché un message game over, mais souhaitez que le jeu se relance automatiquement après quelques secondes? un timer est ce qu’il vous faut.
  • Activer ou désactiver un statut après une durée donnée : vous souhaitez que votre personnage ne puisse tirer que toutes les 3 secondes (mécanisme de cool down)? qu’il deviennent invincible pour 10 secondes seulement lorsqu’il a ramassé une étoile, ou pendant un cours laps de temps après  avoir touché un ennemi? Les timers sont la solution.
  • Activer des actions répétitives dans le temps : augmenter la valeur d’un chronomètre ? faire un compte à rebours? créer une nouvelle vague d’ennemis toutes les minutes ?  Timer, timer timer!

Vous l’aurez compris, les timers offrent une solution élégante et pertinente à de nombreux problèmes (ret se complètent plutot bien avec un autre mécanisme puissant que sont les tweens).

Dans ce tutoriel, nous explorons la syntaxe générale des timers, et présentons ensuite des morceaux de syntaxe suffisamment significatifs pour quelques exemples.

Les notions abordées sur ce tutoriel sont : 

  • la syntaxe générale d’un timer,
  • l’activation / désactivation de timer différés
  • la mise en place de timers rérurrents

Démo du résultat à obtenir avec ce tutoriel

Démo à faire :

  • Disparition d’un message de consignes
  • Fonction de tir avec colddown
  • Invincibilité pendant 3 secondes après contact avec adversaire
  • Faire apparaitre une étoile aléatoire toutes les 5 à 8 secondes
  • Faire apparaitre un ennemi  toutes les 4 secondes.

Base de départ pour ce tutoriel

Nous partons d’une base constituée d’un jeu dans lequel un personnage est présent sur un monde de taille fixe avec une gravité prédéfinie. Ce personnage peut aller à gauche, à droite, et sauter avec la flèche du haut s’il touche le sol. Les animations sur ce personnage ont été définies. Cette base a été créée à partir du tutoriel  Créer son premier jeu de plate-forme en découvrant Phaser sur lequel on a enlevé toute la partie “plateformes flottantes”,  “génération des étoiles”, “génération du score”, “génération des bombes”. 

Figure 1 : un personnage pouvant bouger dans un monde simple

Cette base est directement accessible sur codesandbox sous forme de template: lorsque vous créez une nouvelle sandbox, choisissez « explore templates » et et recherchez dans la barre de recherche « phaser-base»

I - Présentation des timers

Un timer est un élément sensé exécuter une ou plusieurs fois une action représentée par une fonction de callback (rappel). Ce timer peut-être déclenché dès sa création, être stoppé, relancé … les possibilités sont nombreuses. Phaser distingue en gros 3 types de timers :

  1. les timer one-shot : ils permettent de délayer simplement l’exécution d’une fonction de callback.
  2. les timers en boucle infinie : la fonction de callback se répète indéfiniment jusqu’à ce que le timer soit stoppé ou détruit.
  3. les timers en nombre de répétition fixe : on annonce à l’avance le nombre de fois qu’un timer va être répété. Notez que ce dernier type est le plus générique : en mettant le nombre de répétitions à 0, on obtient un timer one-shot, avec un nombre de répétitions à -1, on obtient un timer en boucle infinie.
Nous présentons ci-après chacun de ces timers avec quelques exemples significatifs.

II - Les timer one-shot

Un timer one-shot se définit  avec la méthode delayedCall(). Il bénéficie d’une notation allégée pour exécuter simplement une action après un certain laps de temps. On peut néanmoins leur passer un contexte d’exécution.

I.1 Syntaxe de base

 La syntaxe d’appel de ce timer, à l’intérieur d’une scène (identifiée ici par this) est la suivante :

var monTimer = this.time.delayedCall(delai, callback, args, scope);  

Ce timer prend 4 paramètres. Les différents paramètres utilisés sont les suivants :

  • delai est le délai en millisecondes à attendre avant d’exécuter la fonction de callback
  • callback est l’identifiant  la fonction à exécuter après expiration du délai (sans les parentheses)!
  • args est le tableau des arguments à passer à la fonction callback si cette dernière a des paramètres. Si la fonction n’a pas de paramètres, on mettra null
  • scope est le contexte dans lequel va s’exécuter la fonction. Généralement, on utilisera this pour permettre l’accès aux variables qui sont définies dans l’environnement de création du timer..
Point important : pour le paramètre  callback, on peut utiliser le nom d’une fonction déjà défini, mais on peut aussi directement écrire le code de la fonction de callback dans le timer! Cette notation en apparence plus lourde permet d’avoir la fonction de callback directement écrite dans le callback pour une meilleure lisibilité; les 2 écritures sont présentées dans l’exemple ci-desous.
 

Ou faut-il créer ces timers?? dans la fonction create()? la fonction update()?  Généralement on les crée pour utilisation unique, dès qu’on en a besoin. Contrairement à d’autres timers, on ne va pas les « programmer » à l’avance puis les stopper / reprendre. Ils seront généralement créés durant la boucle de jeu update(), dans une fonction appelée par update(), ou dans une fonction appelée lors d’une collision, d’un overlap ou de tout autre événement de jeu.

I.3 Exemple : Relancer le niveau à la mort du personnage après un certain délai

Premier exemple : supposons que nous avons  une fonction recommencerNiveau() qui relance le niveau une fois le personnage mort, mais nous voulons attendre 3 secondes apres la mort du personnage pour relancer le niveau. En dehors de toute fonction preload / create / update, on écrira d’abord la fonction recommencerNiveau() comme suit :

function recommencerNiveau() {
    this.scene.restart();
} 

Et lorsque l’on veut lancer cette fonction avec un délai de 3 secondes, on écrira :

var timerRestart = this.time.delayedCall(3000, recommencerNiveau,null, this);   

Notez ici les 2 points suivants: 

  •  l’absence de paramètres pour la fonction (rien entre les parenthèses sur sa déclaration), donc args est un tableau vide  []
  • l’utilisation de this.scene.restart() dans le code de la fonction. Le this de cette instruction  doit être le même que  le this qui appelle le timer, en l’occurence la scene. C’est une question d’environnement d’appel, pas toujours simple à comprendre en javascript. Ceci implique de définir le scope du timer avec this.
Enfin comme dit précédemment, on peut écrire la fonction directement dans la zone de callback du timer pour avoir le callback sous les yeux. Le code suivant fait la même chose que les 2 blocs précédents :  
var timerRestart = this.time.delayedCall(3000,
  function recommencerNiveau() {
    this.scene.restart();
  },
  null, this);   

Comme l’identifiant recommencerNiveau n’est plus vraiment utile puisque la fonction est directement présente, on peut même  le supprimer, et écrire simplement : 

var timerRestart = this.time.delayedCall(3000,
  function () {
    this.scene.restart();
  },
  null, this);   

I.2 Exemple : faire une fonction de tire avec délai de cooldown entre deux tirs

Supposons un jeu dans lequel le personnage principal player peut tirer une balle avec une fonction tirer() (comme présenté dans le tutoriel ajout d’une fonction de tir). On veut instaurer un délai entre deux tirs. Il nous suffit de définir sur le personnage un attribut peutTirer de type booléen, qui prendra la valeur true quand ce dernier peut tirer, et false tout le temps où son arme est en train de recharger. Techniquement, il suffit de conditionner le tir à la valeur true du booléen, de passer le booléen à false dès qu’un tir a été effectué, et de le remettre à true au bout d’un certain délai (par ex 2 secondes). On pourra par exemple écrire : 

fucntion tirer (player) {
    if (player.peutTirer == true) {
        // code de génération de la balle 
        // ...
        player.peutTirer = false; // on désactive la possibilté de tirer
        
        // on la réactive dans 2 secondes avec un timer
        var timerTirOk = this.time.delayedCall(2000,
           function () {
            player.peutTirer = true;
        },
        null, this);  
    }
} 

On n’oubliera pas d’initialiser l’attribut peutTirer du player avec le valeur true lors de la création du player, autrement notre personnage ne pourra jamais tirer. Dans la fonction create() on rajoutera apres la création du player la ligne :

player.peutTirer = true; 

Dernier point, important : notez que dans la fonction tirer(), il faut utiliser une référence sur this, qui désigne le contexte appelant, à savoir la scène.  Il faut également pouvoir avoir accès au joueur player. , qui lui aussi est normalement défini dans la scène, donc dans le contexte appelant . Au lieu d’appeler la fonction sans contexte avec l’instruction tirer(player); , on va remplacer l’appel par la ligne suivante :

tirer.call(this, player); 

Cette ligne fait exactement la même chose, à savoir exécuter la fonction tirer(), mais elle lui passe en plus le contexte this qui va nous permettre d’exécuter les instructions de timer.

II - Les timer en boucle infinie ou en répétition multiples

Un timer qui se répète se définit  avec la méthode addEvent() de la classe scene.time. Cette méthode retourne une référence vers un objet de type Timer. Il a une notation plus complexe : il prend en paramètre une liste entre { } contenant différents couples paramètre : valeur séparés par des virgules. On y retrouve les arguments des timer one-shot mais présentés différemment.

II.1 Syntaxe de base et manipulation

var monTimer = this.time.addEvent({
    delay: delai,                // ms
    callback: callback,
    args: [],
    callbackScope: scope,
    repeat: nbRepetitions
}); 

Ici ce timer prend une liste composée de 5 couples paramètres : valeur. Les différents paramètres utilisés sont les suivants :

  • delai est le délai en millisecondes à attendre avant d’exécuter la fonction de callback
  • callback est l’identifiant  la fonction à exécuter après expiration du délai (sans les parentheses)!
  • args est le tableau des arguments à passer à la fonction callback si cette dernière a des paramètres. Si la fonction n’a pas de paramètres, on mettra null
  • callbackScope est le contexte dans lequel va s’exécuter la fonction. Généralement, on utilisera this pour permettre l’accès aux variables qui sont définies dans l’environnement de création du timer.
  • repeat est le nombre de répétitions que va faire ce timer. Si on souhaite faire un timer qui se répète à l’infini, on peut affecter repeat à -1, ou remplacer repeat par loop et lui affecter la valeur true.
Point important : pour le paramètre  callback, on peut utiliser le nom d’une fonction déjà défini, mais on peut aussi directement écrire le code de la fonction de callback dans le timer! Cette notation en apparence plus lourde permet d’avoir la fonction de callback directement écrite dans le callback pour une meilleure lisibilité; les 2 écritures sont présentées dans l’exemple ci-dessous. 

 

Attention, une fois créé le timer est lancé automatiquement, sauf si on précise de ne pas le lancer  en rajoutant l’option  paused : true à la liste des éléments de configuration.

La méthode addEvent retourne une référence. Si cette référence est stockée dans une variable, ici monTimer, alors il devient possible de manipuler le timer en cours de jeu, c’est à dire : le mettre en pause, le relancer, le réinitialiser.  ainsi on pourra utliser les actions suivantes :

 

monTimer.paused = true;  // met en pause le timer
monTimer.paused = false; // relance le timer
monTimer.remove();  // arrete le timer, le met à expiration. 

Ou faut-il créer ces timers?? dans la fonction create()? la fonction update()? On peut mettre autant de timer que l’on veut, ou on le souhaite. Néanmoins, une bonne pratique consiste à définir en amont tous les timer dont on aurait besoin dans la fonction create() et les activer / désactiver durant les événements de la boucle de jeu dans update().

II.2 Exemple : le joueur saute automatiquement toutes les 5 secondes

Partons d’un jeu avec un personnage défini et référencé par player (par exemple via une nouvelle sandbox créée à partir du template phaser_base ) Rajoutons ce code en fin de fonction create() , après la création du player. alors toutes les  5 secondes le sprite player effectuera un petit saut.

  var monTimer = this.time.addEvent({
    delay: 5000, // ms
    callback: function () {
      player.setVelocityY(-200);
    },
    args: [],
    callbackScope: this,
    repeat: -1
  }); 

Webographie

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

18 + = 28