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”.
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 :
- les timer one-shot : ils permettent de délayer simplement l’exécution d’une fonction de callback.
- 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.
- 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.
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..
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.
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.
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
});