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(). La balle. 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

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.

Un timer one-shot bénéficie d’une notation allégée pour exécuter simplement une action après un certain laps de temps.  La syntaxe d’appel  de ce timer, à l’intérieur d’une scene (identifiée ici par this) est la suivante :

II.1 Syntaxe de base​

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

Nous créons ensuite le tween. Un tween repose sur une configuration sous forme de liste json. 

Comme nous allons définir  notre tween dans la fonction create() mais l’activerons / désactiverons dans la fonction update(), il est nécessaire de pouvoir y accéder dans ces deux fonctions, donc de créer la référence du tween en dehors de toute fonction. Ainsi, ajoutez en variable globale la ligne suivante :

var tween_mouvement; 

Créons maintenant notre tween. Un tween repose entièrement  sur une configuration sous forme de liste json dans laquelle on décrit de nombreuses options de morphing, de déplacement, et de temporalité. Ici nous n’utilisons  qu’une petite partie des options de configuration possibles. Mais il est même possible d’associer des fonctions de callback (rappel) à différents moments de l’exécution d’un tween. 

Le mouvement envisagé ici est le suivant : lorsqu’activée, la plate-forme va monter de 300 pixels en 2 secondes, puis faire une pause de 1 seconde, puis revenir à son point de départ en 2 secondes, attendre à nouveau 1 seconde et recommencer en boucle.

Dans la fonction create() , après la création de plateforme_mobile, on crée notre tween en ajoutant les instructions suivantes. La signification de chaque élément est présenté en commentaire et détaillée ci-après :

 tween_mouvement = this.tweens.add({
    targets: [plateforme_mobile],  // on applique le tween sur platefprme_mobile
    paused: true, // de base le tween est en pause
    ease: "Linear",  // concerne la vitesse de mouvement : linéaire ici 
    duration: 2000,  // durée de l'animation pour monter 
    yoyo: true,   // mode yoyo : une fois terminé on "rembobine" le déplacement 
    y: "-=300",   // on va déplacer la plateforme de 300 pixel vers le haut par rapport a sa position
    delay: 0,     // délai avant le début du tween une fois ce dernier activé
    hold: 1000,   // délai avant le yoyo : temps qeu al plate-forme reste en haut
    repeatDelay: 1000, // deléi avant la répétition : temps que la plate-forme reste en bas
    repeat: -1 // répétition infinie 
  });
 

Détail des éléments de la liste :

(a faire 😉  mais les commentaires sont assez parlants)

Notre tween est prêt mais inactif. Pour lancer un tween, on pourra utiliser sa méthode resume(). Pour le mettre en pause, on utilisera sa méthode pause()Dans une seconde partie, nous allons le relier à un levier et l’activer / désactiver à volonté.

II - Ajout du levier et activation du Tween

Nous créons ici un levier et son interaction. Cette interaction se fait si deux conditions sont réunies : 1) le personnage est sur le levier et 2) il appuie sur la barre espace.

II.1 - Chargement de l’asset, et placement du levier

Nous allons utiliser le spritesheet levier présenté ci-dessous (clic droit sur l’image et enregistrer pour le récupérer). Il s’agit d’une image de 60 x 60 pixels. L’avantage de cette image est sa symétrie : en mode miroir, le levier semble activé de l’autre coté.

Nous plaçons le fichier  levier.png dans le répertoire src/assets. Dans la fonction preload(), on charge notre fichier levier.png que l’on référence avec le mot clé “img_levier” :

this.load.image("img_levier", "src/assets/levier.png"); 

En dehors de toute fonction nous ajoutons tout d’abord la variable levier qui va référencer notre levier: 

var levier; 

Nous plaçons ensuite notre levier  en tant que sprite statique; Dans la fonction create() on ajoute la ligne suivante pour placer le levier en coordonnées (700, 538) avec la texture img_levier :

 levier = this.physics.add.staticSprite(700, 538, "img_levier"); 

Notre levier est visuellement placé. Il ne manque plus qu’à l’associer au tween lors d’une activation.

II.2 - Activation du levier et lancement / pause du tween

Pour activer ou désactiver le levier, il va falloir tout d’abord stocker son état dans le jeu. Est-il activé ou désactivé? Le plus simple est de stocker cet élément en tant qu’attribut de levier!  On rajoute donc un attribut active à l’objet levier, qui prendra comme valeur true si le levier est activé, et false sinon. 

Au départ, le levier est désactivé. Dans la fonction create(), après la création de levier, on ajoute :

 levier.active = false; 

Reste plus qu’à activer le levier dans la fonction update(). Nous allons utiliser 2 fonctions de Phaser particulièrement utiles (déjà présentées en détail dans le tutoriel ouvrir une porte en appuyant sur espace) :

  • Phaser.Input.Keyboard.JustDown() : permet de détecter quand une touche vient d’etre pressée. 
  • this.physics.overlap() est une fonction qui prend deux paramètres de type sprite, et renvoie true si les hitbox de ces deux objets s’intersectent  (this est un élément plus complexe qu’il n’y parait. Il représente ici la scène en question)

L’algorithme à mettre en place est le suivant : si les deux conditions “appui sur une touche espace” et “superposition entre la hitbox du player et celle du levier” sont vérifiées, on lance le code d’activation / désactivation du levier :

  1. Activer levier revient à faire passer son état levier.active à true, à inverser son image de texture (levier.flipX à true) et à lancer le tween tween_mouvement grâce à sa méthode resume().
  2. Desactiver levier revient à faire passer son état levier.active à false, à ne pas inverser son image de texture (levier.flipX à false) et à mettre en pause le tween tween_mouvement grâce à sa méthode pause().

 Nous réutilisons ici la variable clavier que nous avions défini en tant qu’écouteur clavier avec un createCursorKeys() dans le tutoriel de base. Dans la fonction update() on ajoute donc le code suivant qui décrit précisément l’algorithme évoqué :

  // activation du levier : on est dessus et on appuie sur espace
  if (
    Phaser.Input.Keyboard.JustDown(clavier.space) == true &&
    this.physics.overlap(player, levier) == true
  ) {
    // si le levier etait activé, on le désactive et stoppe la plateforme
    if (levier.active == true) {
      levier.active = false; // on désactive le levier
      levier.flipX = false; // permet d'inverser l'image
      tween_mouvement.pause();  // on stoppe le tween
    }
    // sinon :  on l'active et stoppe la plateforme
    else {
      levier.active = true; // on active le levier 
      levier.flipX = true; // on tourne l'image du levier
      tween_mouvement.resume();  // on relance le tween
    }
  } 

Il ne vous reste plus qu’à tester votre levier en l’activant / désactivant plusieurs fois.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.

WC Captcha + 85 = 95