Pong en javascript – gestion du score
Neuvième partie consacrée au développement d’un jeu vidéo html5 javascript : gestion du score et engagement. Aujourd’hui, on ajoute la gestion du score ainsi que l’engagement de la balle par le joueur humain et par l’intelligence artificielle.
Prérequis pour la gestion du score
Avoir lu les tutoriaux consacrés à l’initialisation du projet Coder le jeu vidéo Pong. 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. L’animation de la balle Coder le jeu vidéo Pong – Animer la balle et au contrôle de la raquette par le joueur à l’aide du clavier Coder le jeu vidéo Pong – Animer les raquettes. Le contrôle de la raquette par le joueur à l’aide de la souris Coder le jeu vidéo Pong – Controle à la souris. Le renvoi de la balle par les raquettes Coder le jeu video pong – Renvoi de la balle. La gestion des effets sonores Coder le jeu video pong – Ajout des effets sonores. La gestion de l’IA Coder le jeu vidéo Pong –Intelligence artificielle.
La remise en jeu par le joueur humain
Jusqu’à présent, dès que vous affichez la page web du jeu vidéo, la balle s’engage immédiatement. Le jeu démarre. Or il faut laisser le joueur remettre en jeu la balle via la barre espace du clavier ou d’un clic souris.
Il faut donc distinguer 2 états pour la balle :
– elle est en jeu;
– elle n’est pas en jeu.
Pour matérialiser ces 2 états, ajoutez dans l’objet ball une nouvelle propriété inGame que vous positionnez à false. L’ état indique que la balle n’est pas en jeu.
const game = {
....
ball : {
width : 10,
height : 10,
color : "#FFD700",
posX : 200,
posY : 200,
directionX : 1,
directionY : 1,
speed : 1,
inGame : false,
....
},
....
}
La propriété prend la valeur true :
– dès que le joueur met en jeu la balle, lorsqu’il presse la barre espace;
– lorsque le joueur IA met en jeu la balle.
Cette variable prend la valeur false dès que l’un des 2 joueurs perd la balle.
Pour mettre en jeu la balle (et changer l’état de la variable inGame), il choisit d’utiliser soit la barre espace du clavier soit un clic droit souris.
Vous devez donc gérer :
– l’appui sur la barre espace du clavier;
– le clic droit souris.
Souvenez vous que le namespace javascript game.control gère les contrôles.
Le contrôle de la barre espace nécessite de :
– modifier la fonction onKeyDown;
– d’ajouter une constante pour le code de la touche barre espace dans le namespace javascript game.keycode.
Le contrôle du clic droit souris nécessite de rajouter la gestion du clic souris.
Jusqu’à présent, la balle est en perpétuel mouvement. En effet sa position est modifiée par l’usage des variables directionX et directionY.
const game = {
....
ball : {
....
move : function() {
this.posX += this.directionX * this.speed;
this.posY += this.directionY * this.speed;
},
....
},
....
}
Et que ces propriétés ne prennent que 2 valeurs : 1 et -1. En ajoutant comme valeur possible 0, il est possible de stopper son mouvement : this.posX et this.posY n’auront pas leur valeur modifiée. Cette valeur 0 correspond dans ce cas à l’état de balle inGame=0. Le modification de la position de la balle peut aussi être conditionné à l’état de cette variable.
Les 2 stratégies sont possibles.
Ajout de la constante barre espace
Dans le namespace game.keycode, ajoutez la constante SPACEBAR.
game.keycode = {
KEYDOWN : 40,
KEYUP : 38,
SPACEBAR : 32
}
Modification de la fonction onKeyDown
Il suffit d’ajouter une nouvelle clause if qui teste la valeur de la touche pressée qui est dans ce cas la barre espace.
Lorsqu’on presse la touche, la balle est mise en jeu par le biais de la propriété inGame.
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;
}
if ( event.keyCode == game.keycode.SPACEBAR ) {
game.ball.inGame = true;
}
},
....
}
Modification de la fonction ball.move
Pour que l’appui sur la touche espace ait un effet, il suffit de l’intégrer dans la fonction move de l’objet ball. La position de la balle via les propriétés posX et posY n’est modifiée que si la balle est en jeu.
const game = {
....
ball : {
....
move : function() {
if ( this.inGame ) {
this.posX += this.directionX * this.speed;
this.posY += this.directionY * this.speed;
}
},
....
},
....
}
L’engagement de la balle par le joueur humain est opérationnel. Il reste à automatiser celui du l’intelligence artificielle qui ne peut se faire que lorsque la balle est perdue.
Au préalable, il faut faire en sorte que le joueur humain ou l’intelligence artificielle perde la balle.
La perte de balle par le joueur ou l’intelligence artificielle
La problématique est simple. L’un des joueurs perd la balle s’il ne la renvoie pas, lorsqu’elle passe derrière lui.
Il suffit donc de tester la position de la balle sur l’axe horizontal (abscisses) pour détecter la perte ou pas de la balle :
– ball.posX < playerOne.posX : la balle est perdue pour le joueur de gauche;
– ball.posX > playerTwo.posX : la balle est perdue pour le joueur de droite.
Créez une nouvelle méthode rattachée à l’objet ball chargée de détecter si elle est perdue ou pas par un joueur :
– renvoie true si elle est perdue;
– revoie false dans le cas contraire.
Je vous propose de la nommer lost.
....
ball : {
....
lost : function(player) {
var returnValue = false;
if ( player.originalPosition == "left" && this.posX < player.posX - this.width ) {
returnValue = true;
} else if ( player.originalPosition == "right" && this.posX > player.posX + player.width ) {
returnValue = true;
}
return returnValue;
}
....
}
....
Appelez cette fonction 2 fois :
– une fois pour le joueur de gauche;
– une fois pour le joueur de droite.
Je vous invite à encapsuler ces 2 appels dans une fonction dédiée au sein du namespace javascript game. Cette même fonction a aussi la charge des actions à réaliser lorsque la balle est perdue.
const game = {
....
lostBall : function() {
if ( this.ball.lost(this.playerOne) ) {
// action si joueur de gauche perd la balle
} else if ( this.ball.lost(this.playerTwo) ) {
// action si joueur de droiteperd la balle
}
},
....
}
Il ne reste plus qu’à l’appeler dans la boucle d’exécution main.
const main = function() {
// le code du jeu
game.clearLayer(game.playersBallLayer);
game.movePlayers();
game.displayPlayers();
game.moveBall();
game.lostBall();
game.ai.move();
game.collideBallWithPlayersAndAction();
// rappel de main au prochain rafraichissement de la page
requestAnimId = window.requestAnimationFrame(main);
}
En l’état, il ne se passe rien puisqu’aucune action n’est indiquée.
Les actions à intégrer sont :
– l’incrément du score pour le joueur qui marque le point;
– l’arrêt de la balle pour une remise en jeu.
La gestion du score
Les règles sont simples :
– dès qu’un joueur n’arrive pas à renvoyer la balle, le compteur du score de l’adversaire s’incrémente de 1 point;
– le joueur qui perd la balle fait la remise en jeu.
Définissez une nouvelle variable score dans chacun des objets playerOne et playerTwo.
const game = {
playerOne : {
width : 10,
height : 50,
color : "#FFFFFF",
posX : 30,
posY : 200,
goUp : false,
goDown : false,
originalPosition : "left",
score : 0
},
playerTwo : {
width : 10,
height : 50,
color : "#FFFFFF",
posX : 650,
posY : 200,
goUp : false,
goDown : false,
originalPosition : "right",
score : 0
},
A la perte de la balle, le score s’incrémente depuis la fonction lostBall du namespace javascript game.
const game = {
....
lostBall : function() {
if ( this.ball.lost(this.playerOne) ) {
this.playerTwo.score++;
} else if ( this.ball.lost(this.playerTwo) ) {
this.playerOne.score++;
}
},
....
}
En l’état, le score des joueurs s’incrémente. Par contre, cela n’est pas rafraichi à l’écran. Il suffit de le rajouter à la fin de la fonction. Effaçez le score puis réaffichez le à l’aide des fonctions dédiées définies dans un précédent article.
const game = {
....
lostBall : function() {
if ( this.ball.lost(this.playerOne) ) {
this.playerTwo.score++;
} else if ( this.ball.lost(this.playerTwo) ) {
this.playerOne.score++;
}
this.scoreLayer.clear();
this.displayScore(this.playerOne.score, this.playerTwo.score);
},
....
}
Aïe!!! Ce n’est pas suffisant. Le score change effectivement mais il continue de s’incrémenter. La balle est toujours derrière la raquette, la boucle d’exécution continue de tourner. Le score s’incrémente encore et encore.
Une solution serait de stopper la boucle d’exécution. Mais dans ce cas tout serait stoppé, y compris le mouvement de la raquette. Cela ne permet donc pas au joueur de choisir l’endroit à partir duquel il engage la balle. Cette solution n’est pas la bonne.
Autre solution, conditionner le test de la balle perdue à ce qu’elle soit effectivement en jeu.
Cette solution utilise la valeur de la variable inGame. Il y a deux façons très proches d’implémenter la solution:
– dans le premier cas, vous implémentez le test dans fonction lostBall;
– dans le deuxième cas, vous implémentez le test en dehors de la fonction lostBall depuis la fonction main.
Ma préférence va à la seconde solution. La méthode lostBall répond à une fonctionnalité dédiée. Lui intégrer un test sur la mise en jeu la ferait sortir de son périmètre de responsabilité. Ce qui donne.
const main = function() {
// le code du jeu
game.clearLayer(game.playersBallLayer);
game.movePlayers();
game.displayPlayers();
game.moveBall();
if ( game.ball.inGame ) {
game.lostBall();
}
game.ai.move();
game.collideBallWithPlayersAndAction();
// rappel de main au prochain rafraichissement de la page
requestAnimId = window.requestAnimationFrame(main);
}
La balle et le score s’arrêtent. Appuyez sur la barre espace pour remettre en jeu la balle. Vous constatez que la balle continue sa course, s’arrête et le score s’incrémente de 1.
Pour parfaire la chose, vous devez lors de la remise en jeu modifier les coordonnées de la balle. Ceci de façon à ce qu’elle soit sur le terrain et son sens de déplacement sur les 2 axes.
De bon sens, la position de la balle lors de la mise en jeu devrait se faire au niveau de la raquette et dans le sens opposé du joueur.
Tout ça se fait sur l’engagement de la balle à l’appui sur la barre espace dans le namespace javascript game.control.
if ( event.keyCode == game.keycode.SPACEBAR) {
game.ball.inGame = true;
game.ball.posX = game.playerOne.posX + game.playerOne.width;
game.ball.posY = game.playerOne.posY;
game.ball.directionX = 1;
game.ball.directionY = 1;
}
Tout fonctionne. Sauf si vous appuyez sur la barre espace pendant une phase de jeu. La balle disparait et repart depuis la raquette. L’appui sur la barre espace ne doit avoir un effet que lorsque la balle n’est pas en jeu (game.ball.inGame=false).
if ( event.keyCode == game.keycode.SPACEBAR && !game.ball.inGame ) {
game.ball.inGame = true;
game.ball.posX = game.playerOne.posX + game.playerOne.width;
game.ball.posY = game.playerOne.posY;
game.ball.directionX = 1;
game.ball.directionY = 1;
}
Il ne reste plus qu’à faire en sorte que l’IA engage elle-même.
Commencez par ajouter une fonction dédiée à la mise en jeu dans le namespace javascript game.ai.
game.ai = {
....
startBall : function() {
if ( this.player.originalPosition == "right" ) {
this.ball.inGame = true;
this.ball.posX = this.player.posX;
this.ball.posY = this.player.posY;
this.ball.directionX = -1;
this.ball.directionY = 1;
} else {
this.ball.inGame = true;
this.ball.posX = this.player.posX + this.player.width;
this.ball.posY = this.player.posY;
this.ball.directionX = 1;
this.ball.directionY = 1;
}
}
}
Elle prend en considération quelle raquette est intelligence artificielle. Elle se charge de réinitialiser la balle et son mouvement lorsque l’IA perd la balle.
Elle doit être appelée automatiquement lorsque la balle est perdue par l’IA, après un certain délai. Utilisez la fonction setTimeout pour appeler cette fonction de manière différée, au bout de 3 secondes, comme ceci.
setTimeout(game.ai.startBall(), 3000);
La gestion de la perte de la balle passe par la fonction lostBall du namespace javascript. Il convient donc de faire appel à startBall de cette même fonction. Cependant, il ne faut le faire que lorsque l’IA perd la balle. Savoir quelle raquette est contrôlée par l’IA permettrait de décider de la raquette qui engage la balle.
Ajoutez une nouvelle propriété booléenne ai aux objets player (playerOne et playerTwo) indicatrice d’une intelligence artificielle.
playerOne : {
width : 10,
height : 50,
color : "#FFFFFF",
posX : 30,
posY : 200,
goUp : false,
goDown : false,
originalPosition : "left",
score : 0,
ai : false
},
playerTwo : {
width : 10,
height : 50,
color : "#FFFFFF",
posX : 650,
posY : 200,
goUp : false,
goDown : false,
originalPosition : "right",
score : 0,
ai : true
},
Par défaut, on indique que la raquette de gauche (playerOne) est contrôlée par un joueur humain (ai : false), et que celle de droite (playerTwo) par l’intelligence artificielle (ai : true).
Lorsqu’un joueur perd la balle et qu’il est l’intelligence artificielle alors le lancement de la balle se fait par le biais de la fonction game.ai.startBall().
lostBall : function() {
if ( this.ball.lost(this.playerOne) ) {
this.playerTwo.score++;
this.ball.inGame = false;
if ( this.playerOne.ai ) {
setTimeout(game.ai.startBall(), 3000);
}
} else if ( this.ball.lost(this.playerTwo) ) {
this.playerOne.score++;
this.ball.inGame = false;
if ( this.playerTwo.ai ) {
setTimeout(game.ai.startBall(), 3000);
}
}
this.scoreLayer.clear();
this.displayScore(this.playerOne.score, this.playerTwo.score);
}
Le code de démo a désactivé la fonction move de l’intelligence artificielle.