Pong en javascript – animer les raquettes
Comment animer les raquettes dans le jeu Pong? Quatrième partie consacrée au développement d’un jeu vidéo html5 javascript. L’objectif du jour est d’animer la raquette du joueur par le biais des touches du clavier.
Prérequis pour animer les raquettes dans le jeu Pong
Avoir lu les tutoriaux consacrés à l’initialisation du projet Coder le jeu vidéo Pong 1ere partie. La mise en place de l’environnement du jeu constitué du terrain, du filet, des raquettes, de la balle et du score Coder le jeu vidéo Pong – Raquettes et Balle et à l’animation de la balle Coder le jeu vidéo Pong – Animer la balle.
Le principe de l’animation des raquettes dans le jeu Pong
Celui-ci va consister à bouger la raquette du joueur :
– vers le haut lorsqu’il appuiera sur la touche flèche haut;
– vers le bas lorsqu’il appuiera sur la touche flèche bas.
Javascript met à disposition du développeur un mécanisme qui permet de détecter si on appuie sur une touche du clavier. Ce mécanisme passe par le biais des événements :
– onkeydown permet de détecter lorsqu’une touche est pressée;
– onkeyup permet de détecter lorsqu’une touche est relevée après avoir été pressée.
Ces deux événements ne distinguent pas les touches sur lesquelles l’utilisateur a appuyé. Cette partie doit être gérée par le développeur.
Les touches de contrôle de la raquette
Souvenez vous de l’instruction…
window.onload = initialisation;
Elle associe à l’événement onload (chargement de la page) une fonction initialisation chargée d’initialiser le jeu.
Pour les événements onkeydown et onkeyup, le principe est identique. Nous allons leur associer à chacun une fonction Javascript de manière construite.
Il se peut que vous retrouviez souvent dans vos lectures ce type de déclaration, comme ci-dessous.
// association des méthodes aux évènements :
window.onkeydown = onKeyDown;
window.onkeyup = onKeyUp;
Pour structurer le code, je vous propose de créer un nouveau namespace dédié au contrôle dans un fichier dédié game.control.js. N’oubliez pas de l’intégrer comme source dans le fichier game.html.
game.control = {
}
Ce namespace va encapsuler les fonctions qui seront appelées sur l’appui sur les touches.
displayBall : function() {
game.display.drawRectangleInLayer(this.playersBallLayer, this.ball.width, this.ball.height, this.ball.color, this.ball.posX, this.ball.posY);
},
Pour rendre la relecture du code plus simple, encapsulons l’appel de ces 2 fonctions dans une autre. Nous l’appellerons moveBall et nous la rattacherons au namespace javascript game.js.
game.control = {
onKeyDown : function(event) {
},
onKeyUp : function(event) {
}
}
L’association événement/fonction est confiée au namespace racine game.js. Ceci par le biais d’une nouvelle fonction que nous appelons initKeyboard.
const game = {
....
initKeyboard : function(onKeyDownFunction, onKeyUpFunction) {
window.onkeydown = onKeyDownFunction;
window.onkeyup = onKeyUpFunction;
}
};
En l’état, il n’y a pas d’association puisque la fonction initKeyboard n’est pas appelée. Cette fonction n’a vocation à être appelée qu’une seule fois lors de l’initialisation et donc depuis la fonction dédiée init.
init : function() {
....
this.displayScore(0,0);
this.displayBall(200,200);
this.displayPlayers();
this.initKeyboard(game.control.onKeyDown, game.control.onKeyUp);
},
La structure est en place. Il ne reste plus qu’à l’utiliser pour animer les raquettes dans le jeu Pong que nous développons.
Les fonctions game.control.keyDown et game.control.keyUp en l’état réagissent à l’appui sur n’importe quelle touche du clavier.
Pour que seules les touches flèche haut et flèche bas soient les seules réactives pour le jeu, il est nécessaire d’appliquer un filtre. Ce filtre consiste simplement à tester si l’une de ces deux touches est appuyée. Notamment par le biais du code de la touche.
L’objet event des fonctions onKeyDown et onKeyUp comporte diverses propriétés parmi lesquelles figure le code de la touche sélectionnée (event.keycode).
Le code correspondant à :
– la touche flèche haut est 38;
– la touche flèche bas est 40.
Toujours pour une meilleure lisibilité, je vous propose d’encapsuler ces valeurs dans un namespace dédié game.keycode.js. N’oubliez pas de l’intégrer comme source dans le fichier game.html .
game.keycode = {
KEYDOWN : 40,
KEYUP : 38
}
Il ne reste plus qu’à intégrer le test des touches dans les fonctions onKeyDown et onKeyUp.
game.control = {
onKeyDown : function(event) {
if ( event.keyCode == game.keycode.KEYDOWN ) {
} else if ( event.keyCode == game.keycode.KEYUP ) {
}
},
onKeyUp : function(event) {
if ( event.keyCode == game.keycode.KEYDOWN ) {
} else if ( event.keyCode == game.keycode.KEYUP ) {
}
}
}
Le mouvement de la raquette
Pour donner un mouvement à la raquette, modifiez les coordonnées (posX et posY) de l’objet game.playerOne.
Dans le cas présent, la raquette ne bouge que verticalement, seule son ordonnée évolue.
C’est donc la valeur de la variable game.playerOne.posY qui change :
– pour que la raquette monte, sa valeur doit diminuer;
– pour que la raquette descende, sa valeur doit augmenter.
Cependant, la raquette ne doit pas sortir de l’écran par le haut ou par le bas. La valeur de game.playerOne.posY doit avoir une limite basse et une limite haute. La limite basse ne peut être inférieure à 0. La limite haute ne peut être supérieure à la largeur du terrain (groundHeight) à laquelle on soustrait la taille de la raquette (game.playerOne.height).
Il vient naturellement à l’esprit d’incrémenter et de décrémenter la valeur de game.playerOne.posY dans la fonction onKeyDown.
game.control = {
onKeyDown : function(event) {
if ( event.keyCode == game.keycode.KEYDOWN ) {
playerOne.posY+=5;
} else if ( event.keyCode == game.keycode.KEYUP ) {
playerOne.posY-=5;
}
},
onKeyUp : function(event) {
if ( event.keyCode == game.keycode.KEYDOWN ) {
} else if ( event.keyCode == game.keycode.KEYUP ) {
}
}
}
Et d’y ajouter les tests de dépassement d’écran.
game.control = {
onKeyDown : function(event) {
if ( event.keyCode == game.keycode.KEYDOWN && game.playerOne.posY < game.groundHeight - game.playerOne.height ) {
playerOne.posY+=5;
} else if ( event.keyCode == game.keycode.KEYUP && game.playerOne.posY > 0 ) {
playerOne.posY-=5;
}
},
onKeyUp : function(event) {
if ( event.keyCode == game.keycode.KEYDOWN ) {
} else if ( event.keyCode == game.keycode.KEYUP ) {
}
}
}
En l’état le mouvement de la raquette fonctionne. Toutefois, visuellement parlant, cela manque de fluidité. Tentez de bouger la raquette vers le haut puis immédiatement vers le bas, vous constatez un certain temps de latence pour passer d’un mouvement à l’autre.
L’explication est très simple. Les évènements liés à l’appui sur les touches (et donc la modification de la position de la raquette) sont désynchronisés de l’affichage. La détection de l’appui sur une touche ne se fait pas en même temps que l’affichage (dans le même cycle).
Pour rappel, un cycle d’affichage correspond à un passage dans la fonction main.
La solution consiste à synchroniser les mouvements de la raquette avec l’affichage. En ne modifiant la position de la balle qu’une fois par cycle d’affichage. Pour cela, il suffit de créer une fonction movePlayers. Elle modifiera la position de la raquette en modifiant la valeur de game.playerOne.posY . Celle ci sera appelée par la fonction principale juste avant l’appel de la fonction displayPlayers.
Cette solution nécessite de modifier les fonctions onKeyDown et onKeyUp de manière à ce qu’elle positionne deux indicateurs. L’un indique que la touche flèche haut est pressée et l’autre indique que la touche flèche bas est pressée.
Commençons par créer les indicateurs goUp et goDown. Ceux-ci relevant du joueur, je propose de les intégrer dans les objets game.playerOne et game.playerTwo.
const game = {
...
playerOne : {
width : 10,
height : 50,
color : "#FFFFFF",
posX : 30,
posY : 200,
goUp : false,
goDown : false
},
playerTwo : {
width : 10,
height : 50,
color : "#FFFFFF",
posX : 650,
posY : 200,
goUp : false,
goDown : false
},
...
};
Puis modifiez les fonctions onKeyDown et onKeyUp.
game.control = {
onKeyDown : function(event) {
if ( event.keyCode == game.keycode.KEYDOWN ) {
game.playerOne.goDown = true;
} else if ( event.keyCode == game.keycode.KEYUP ) {
game.playerOne.goUp = true;
}
},
onKeyUp : function(event) {
if ( event.keyCode == game.keycode.KEYDOWN ) {
game.playerOne.goDown = false;
} else if ( event.keyCode == game.keycode.KEYUP ) {
game.playerOne.goUp = false;
}
}
}
Remarquez que dans la première version, la fonction onKeyUp n’est pas utilisée.
Lorsque game.playerOne.goDown ou game.playerOne.goUp sont positionnées à true, si rien ne les repositionne à false, la raquette continuera son mouvement même lorsque les touches sont relâchées.
La fonction onKeyUp relative à l’évènement d’une touche relevée après être pressée repositionne, selon le cas, à false les variables game.playerOne.goDown ou game.playerOne.goUp.
Il ne reste plus qu’à implémenter la fonction movePlayers pour qu’elle incrémente game.playerOne.posY lorsque l’indicateur game.playerOne.goDown est positionné. De même pour qu’elle décrémente game.playerOne.posY lorsque l’indicateur game.playerOne.goUp est positionné.
const game = {
....
movePlayers : function() {
if (game.playerOne.goUp && game.playerOne.posY > 0)
game.playerOne.posY-=5;
else if (game.playerOne.goDown && game.playerOne.posY < game.groundHeight - game.playerOne.height)
game.playerOne.posY+=5;
},
....
}
Puis de l’appeler depuis la fonction main.
const main = function() {
game.clearLayer(game.playersBallLayer);
game.movePlayers();
game.displayPlayers();
game.moveBall();
requestAnimId = window.requestAnimationFrame(main);
}
Vous avez vu comment animer les raquettes dans le jeu Pong. Vos raquettes répondent au doigt et à l’oeil de manière fluide. Le prochain article sera consacré au contrôle de la raquette à la souris, à suivre ici.