Exercice guidé : 10 secondes
Classes Objets Évènements DOM Tableaux Boucles
Dans ce jeu, vous avez 10 secondes pour attraper autant de balles rebondissantes que vous le pouvez en cliquant dessus.
L'animation se compose de balles colorées qui se déplacent dans des directions aléatoires et rebondissent sur les bords du cadre. À chaque fois que le joueur réussit à cliquer sur une balle, le score augmente de 1. À la fin des 10 secondes, le jeu affiche "Fini" et le score.
Voici une démo :
Vous pouvez jouer en "plein écran" ici.
Pour commencer, on déclare la classe BouncingBall
.
// *** Début de la classe BouncingBall ***
class BouncingBall {
}
Le constructeur ne prendra pas de paramètre puisque les caractéristiques des balles seront aléatoires. On aura besoin de plusieurs propriétés pour caractériser une balle :
time
: le compteur de temps de vie de la balle, qui sera exprimé en millisecondesdx
: La position sur l'axe x de la balle, initialisée à une valeur entière au hasard entre 0 et 10dy
: La position sur l'axe y de la balle, initialisée également à une valeur entière au hasard entre 0 et 10width
: La largeur de la balleheight
: La hauteur de la balle, qui sera donc égale à la largeur (c'est un cercle !). Cette valeur sera calculée au hasard entre 80 pixels et 160 pixels.timeoutID
: l'identifiant du timeout qui sera utilisé pour déplacer la balle à intervalles réguliers. On utilisera ici la fonctionsetTimeout
qui permet d'exécuter une fonction une fois après un temps défini.element
: ce sera l'élément HTML représentant la balle, une divplayground
: le terrain de jeu de la balle, l'espace dans lequel elle se déplacera
Voici le code correspondant :
constructor () {
// Initialisation des paramètres de la balle
this.time = 0; // Pour compter les 10 secondes de "vie" de la balle
this.dx = this.getRandomInt(0,10); // Position x de la balle
this.dy = this.getRandomInt(0,10); // Position y
var size = this.getRandomInt(80,160); // Diamètre de la balle (aléatoire)
this.width = size;
this.height = size;
this.timeoutID; //l'ID du timeout
// Initialisation de la div
this.element = document.createElement('div'); // Élément qui représentera la balle
this.element.style.backgroundColor = this.getRandomColor(); // Couleur aléatoire
this.element.style.width = this.width + 'px'; // Dimensions de la div
this.element.style.height = this.height + 'px';
this.element.className += 'ball'; // Pour le CSS
this.playground = document.getElementById('playground');
this.playground.appendChild(this.element); //Ajout de la balle à la div #playground
// TODO Tracé de la balle
// TODO Écouteur des clics sur la balle
}
Quand on aura écrit les méthodes correspondantes, on ajoutera un appel à la méthode permettant de dessiner la balle, ainsi que l'écouteur d'évènements sur la balle.
Vous pouvez voir que j'ai fait appel à deux méthodes getRandomInt
et getRandomColor
qui pour la première tirera un nombre entier au hasard et pour la seconde une couleur au hasard. Ils nous faut donc écrire ces deux méthodes :
getRandomColor(){
// Retourne un code couleur au hasard dans le modèle hsla
return 'hsla(' + (Math.random() * 360) + ', 100%, 50%, 1)';
}
getRandomInt(min, max){
// Retourne un nombre entier aléatoire entre min et max
return Math.floor(Math.random() * (max - min + 1)) + min;
}
Ensuite, pour déplacer une balle, on aura besoin de modifier le style de balle : top
pour le positionnement sur l'axe x et left
pour le positionnement sur l'axe y. On va donc écrire une méthode moveTo
prenant en paramètre les coordonnées x et y où déplacer la balle et qui modifiera les propriétés correspondantes, comme ceci :
moveTo(x, y) {
// Modifie les coordonnées x et y de la balle
this.element.style.left = x + 'px';
this.element.style.top = y + 'px';
}
Il nous reste maintenant deux tâches à programmer : le tracé de la balle à intervalles réguliers et le changement de direction quand la balle atteint les limites du cadre. Regardons le changement de direction. On nommera la méthode changeDirectionIfNecessary
et elle prendra en paramètre les coordonnées x et y de la balle. Si la position x devient négative (déborde du cadre à gauche) ou supérieure à la largeur du cadre, on inverse le déplacement en changeant le signe de la coordonnée x. On procède de la même façon pour l'axe y. Voici le résultat :
changeDirectionIfNecessary(x, y) {
// Modifie la direction de la balle quand elle atteint les limites du terrain
// Limites gauche/droite
if (x < 0 || x > this.playground.getBoundingClientRect().width - this.width) {
this.dx = -this.dx; //demi-tour
}
// Limites bas/haut
if (y < 0 || y > this.playground.getBoundingClientRect().height - this.height) {
this.dy = -this.dy;
}
}
Il nous reste à tracer la balle à intervalles réguliers en faisant appel aux méthodes que l'on a déjà écrites. Ce sera le rôle de la méthode draw
. Si la balle est "en vie" depuis moins de 10 secondes, on rappelera à nouveau la méthode draw
après 18ms avec la fonction setTimeout
, en changeant de direction si nécessaire. Sinon on interrompra le tracé de la balle et on affichera un message "Fini" dans la zone du jeu. La fonction setTimeout
fonctionne différemment de setInterval
: elle appelle la fonction cible une seule fois, après un temps défini (ici 18ms). Regardez bien le code : notre méthode draw
appelle la fonction setTimeout
qui elle-même appelle la méthode draw
qui rappelera donc la fonction setTimeout
et ainsi de suite : le code est donc répété pendant 10 secondes. Notez l'utilisation de bind(this)
: on veut que this
représente notre balle lorsque la fonction est exécutée.
draw(x, y) {
// Dessine la balle aux coordonnées x et y toutes les 18ms
this.time += 18; /// Le temps passe...
if (this.time <= 10000){ // Si le jeu est commencé depuis moins de 10s
this.moveTo(x, y); // On modifie les coordonnées
this.timeoutID = setTimeout(function () {
this.changeDirectionIfNecessary(x, y); // Chgt de direction
this.draw(x + this.dx, y + this.dy); // Tracé
}.bind(this), 18); // Et ainsi de suite toutes les 18ms
}
else { // Si le jeu est fini
clearTimeout(this.timeoutID);
this.playground.innerHTML = "Fini
";
}
}
Il nous faut maintenant compléter le constructeur. À la création d'une balle, on appelera directement la méthode draw
pour débuter l'animation avec des coordonnées x et y aléatoires. On doit également implémenter l'écouteur d'évènements. Lorsque la souris est pressée sur la div représentant la balle (le joueur a "attrapé" la balle), on met à jour son score dans l'élement HTML correspondant sur la page, on supprime la balle du DOM et on arrête son déplacement. Il ne faut pas oublier ici non plus d'utiliser la méthode bind
en lui donnant en paramètre this
puisqu'on veut que this
représente notre balle dans le corps de la fonction. Voici le code :
constructor () {
.
.
.
// Tracé de la balle
this.draw(this.getRandomInt(0,300), this.getRandomInt(0,300)) //Vers des coordonnées au hasard
// Écouteur des clics sur la balle
this.element.addEventListener("mousedown", function(){
// Mise à jour du score
var total = parseInt(document.getElementById("score").textContent) +1;
document.getElementById("score").textContent = total;
// On retire la balle
this.element.remove();
// On arrête le déplacement
clearTimeout(this.timeoutID);
}.bind(this));
}
Et pour finir, on va créer toutes les balles du jeu à partir de notre classe. Ici j'en crée 30, à l'aide d'une boucle. Chaque balle créée aura donc une couleur et des positions et déplacements aléatoires.
// *** Code principal ***
var NB_BALLS = 30; // Nombre de balles dans le jeu
var balls = []; // Tableau des balles
for (var i = 0; i < NB_BALLS; i++) { // Génération des balles
balls[i] = new BouncingBall();
}
Et voici le programme au complet :
Aller au TP