Pong en javascript – début et fin de partie
Dixième partie consacrée au développement du jeu vidéo html5 javascript: le début et la fin de la partie du jeu. Le développement de Pong est bientôt terminé. Si vous jouez en l’état, les points sont comptés mais une partie ne se termine jamais. Je vous propose donc de faire en sorte qu’une partie ait un début et une fin.
Prérequis pour le début et la fin de partie
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 gestion du score et de l’engagement Coder le jeu vidéo Pong –Score et engagement.
Le principe du début et de la fin de partie du jeu
Une partie débute lorsque le joueur clique sur un bouton dédié Start Game. L’appui sur ce bouton ne fait pas de lancé de balle. Cela reste assuré par la barre espace du clavier. Une partie se déroule jusqu’à ce que l’un des joueurs marque 10 points.
Ajout du bouton pour débuter la partie
La partie démarre sur une action du joueur, en l’occurrence un simple bouton. Ajoutez manuellement ce bouton dans la page html via la balise ‹input type=’button’/› :
– avec un id égal à startGame;
– et une value positionnée à Start Game.
<input id="startGame" type="button" value="Start Game">
Par contre, cela nécessite quelques petites transformations et pas des moindres. Si vous ajoutez le code html ci-dessus directement entre les balises html, le bouton n’est pas visible. Il esta recouvert par le canvas html5 noir.
Pour afficher le bouton Start Game, commencez par l’encapsuler dans une balise div comme ceci.
....
<div id="menu"><input id="startGame" type="button" value="Start Game"></div>
</html>
A l’exécution, vous ne voyez toujours pas le bouton.
Ceci est normal compte tenu de la construction de la fonction createLayer: elle crée un canvas html5. Elle laisse cependant la possibilité de l’encapsuler ou pas dans un container html5.
Si le canvas html5 n’est pas encapsulé. Il sera rattaché à page html5 et disposé brutalement aux coordonnées indiquées par les 2 derniers paramètres de la fonction. C’est le cas auquel vous vous confrontez.
Voir le code ci-dessous.
....
this.groundLayer= game.display.createLayer("terrain", this.groundWidth, this.groundHeight, undefined, 0, "#000000", 0, 0);
....
this.scoreLayer = game.display.createLayer("score", this.groundWidth, this.groundHeight, undefined, 1, undefined, 0, 0);
....
this.playersBallLayer = game.display.createLayer("joueursetballe", this.groundWidth, this.groundHeight, undefined, 2, undefined, 0, 0);
....
Si le canvas html5 est encapsulé. Le container html5 se place aux coordonnées indiquées dans les 2 derniers paramètres.
Du fait de l’ajout du bouton Start Game, l’écran de jeu commence à prendre forme. Il convient de le concevoir proprement en dédiant des zones de l’écran à des fonctions particulières. Dans le cas présent:
– une partie dédiée au menu (existante par le biais de la balise div menu);
– une partie dédiée au terrain de jeu à ajouter.
....
<div id="menu"><input id="startGame" type="button" value="Start Game"></div>
<div id="divGame"></div>
</html>
Au préalable, ajoutez au namespace game une nouvelle propriété. Elle désigne la balise div divGame.
const game = {
....
wallSound : null,
playerSound : null,
divGame : null,
....
}
Il reste à rattacher le layer à la balise div divGame et de placer ce container en dessous du bouton.
init : function() {
this.divGame = document.getElementById("divGame");
this.groundLayer= game.display.createLayer("terrain", this.groundWidth, this.groundHeight, this.divGame, 0, "#000000", 10, 50);
game.display.drawRectangleInLayer(this.groundLayer, this.netWidth, this.groundHeight, this.netColor, this.groundWidth/2 - this.netWidth/2, 0);
this.scoreLayer = game.display.createLayer("score", this.groundWidth, this.groundHeight, this.divGame, 1, undefined, 10, 50);
game.display.drawTextInLayer(this.scoreLayer , "SCORE", "10px Arial", "#FF0000", 10, 10);
this.playersBallLayer = game.display.createLayer("joueursetballe", this.groundWidth, this.groundHeight, this.divGame, 2, undefined, 10, 50);
game.display.drawTextInLayer(this.playersBallLayer, "JOUEURSETBALLE", "10px Arial", "#FF0000", 100, 100);
this.displayScore(0,0);
this.displayBall(200,200);
this.displayPlayers();
this.initKeyboard(game.control.onKeyDown, game.control.onKeyUp);
this.initMouse(game.control.onMouseMove);
this.wallSound = new Audio("./sound/wall.ogg");
this.playerSound = new Audio("./sound/player.ogg");
game.ai.setPlayerAndBall(this.playerTwo, this.ball);
},
A l’affichage.
Le fait que vous ayez 2 parties distinctes permettra ensuite de remodeler votre interface relativement facilement.
La gestion du bouton pour démarrer la partie
Déclencher une partie ou ne pas déclencher une partie. Attention à bien différencier le fait de déclencher une partie et de lancer la balle. Ceux sont 2 états distincts en sachant que l’un est dépendant de l’autre.
On ne peut lancer la balle sans avoir au préalable démarrer une partie.
Le code tel qu’il est intègre une propriété de l’objet ball nommée inGame. Elle indique si la balle est en jeu ou pas. L’état d’une partie démarrée, pour le moment, n’existe pas.
Commencez par ajouter cette propriété au namespace game en la nommant gameOn et en la fixant à false. Au lancement du jeu, la partie n’est pas lancée.
const game = {
....
gameOn : false,
startGameButton : null,
....
Cette nouvelle variable est à positionner à true lorsque l’utilisateur clique sur le bouton Start Game. Il faut abonner le bouton à l’événement onclick. Celui ci génère un appel vers une fonction qui modifie la valeur de la propriété gameOn en la positionnant à true.
Je vous propose de créer une nouvelle méthode dans le namespace game qui abonne le click sur le bouton Start Game à une fonction du namespace game.control onStartGameClickButton qui positionne gameOn à true :
Dans le namespace game.
const game = {
....
initStartGameButton : function() {
this.startGameButton.onclick = game.control.onStartGameClickButton;
},
....
}
Dans le namespace game.control.
game.control = {
....
onStartGameClickButton : function() {
game.gameOn = true;
}
....
}
Ensuite, il faut rendre le lancement de la balle dépendant de l’état de la partie: démarrée ou non démarrée.
Cette modification est à faire au niveau du namespace game.control dans la clause relative à l’appui sur la barre espace:
game.control = {
mousePointer : null,
onKeyDown : function(event) {
....
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;
}
},
....
}
qui devient (la condition game.gameOn a été ajoutée).
game.control = {
....
onKeyDown : function(event) {
....
if ( event.keyCode == game.keycode.SPACEBAR && !game.ball.inGame && game.gameOn ) {
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;
}
},
....
}
Désormais, la partie démarre sur action du joueur. Pour l’arrêt de la partie, c’est dans le paragraphe suivant.
Arrêt de la partie
Comme indiqué en introduction, une partie doit s’arrêter dès que l’un des deux joueurs a marqué 10 points. Un joueur marque un point lorsque son adversaire perd la balle.
C’est la fonction lostBall du namespace game qui détecte la perte de la balle et incrémente le compteur de points.
C’est donc cette même fonction qui devrait stopper la partie une fois les 10 points atteints.
const game = {
....
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);
},
....
}
Il suffit de rajouter un test sur le score de chaque joueur qui, s’il est supérieur à 9, stoppe la partie.
L’indicateur game.gameOn indique si une partie est en cours (gameOn=true) ou pas (gameOn=false). Une simple valorisation de game.gameOn à false suffit à stopper la partie.
Dans le code.
const game = {
....
lostBall : function() {
if ( this.ball.lost(this.playerOne) ) {
this.playerTwo.score++;
if ( this.playerTwo.score > 9 ) {
this.gameOn = false;
} else {
this.ball.inGame = false;
if ( this.playerOne.ai ) {
setTimeout(game.ai.startBall(), 3000);
}
}
} else if ( this.ball.lost(this.playerTwo) ) {
this.playerOne.score++;
if ( this.playerOne.score > 9 ) {
this.gameOn = false;
} else {
this.ball.inGame = false;
if ( this.playerTwo.ai ) {
setTimeout(game.ai.startBall(), 3000);
}
}
}
this.scoreLayer.clear();
this.displayScore(this.playerOne.score, this.playerTwo.score);
},
....
}
La partie est bien stoppée. Cependant, lorsque vous redémarrez une nouvelle partie via le bouton Start Game, le comportement est chaotique. En effet la balle continue sa course là où elle était. Le score s’incrémente et la partie s’arrête.
Tout cela relève du fait que l’état de la balle ainsi que le score ne sont pas réinitialisés.
Deux choix possibles pour cette réinitialisation :
– la réaliser au sein de la fonction lostBall;
– la réaliser lorsque le joueur démarre une partie via le bouton Start Game.
Pour la clarté du code, je vous conseille d’encapsuler tout ce qui est relatif à la réinitialisation d’une partie dans une fonction dédiée rattachée au namespace javascript game. La réinitialisation du jeu et les paramètres sont propres au jeu développé donc non réutilisables.
const game = {
....
reinitGame : function() {
},
....
}
Ensuite, que faut-il réinitialiser ?
– le fait que la balle ne soit plus en jeu;
– le score de chaque joueur et son affichage.
Le résultat.
const game = {
....
reinitGame : function() {
this.ball.inGame = false;
this.playerOne.score = 0;
this.playerTwo.score = 0;
this.scoreLayer.clear();
this.displayScore(this.playerOne.score, this.playerTwo.score);
},
....
}
Mon choix est de l’appeler lorsque le joueur clique sur le bouton Start Game. La fonction appelée dans ce cas est onStartGameClickButton du namespace javascript game.control.
Cet appel se fait uniquement si aucune partie n’est en cours (game.gameOn=false).
game.control = {
....
onStartGameClickButton : function() {
if ( !game.gameOn ) {
game.reinitGame();
game.gameOn = true;
}
}
}
Le début et la fin de partie du jeu Pong sont maintenant installés. Prochain article sur la trajectoire et la vitesse de la balle pour rendre le jeu plus attractif et jouable. C’est ici.
Le code de démo a désactivé la fonction move de l’intelligence artificielle.
Par défaut, on indique que la raquette de gauche (playerOne) est contrôlée par un joueur humain (ai : false). Et la raquette de droite (playerTwo) par l’intelligence artificielle (ai : true).
Lorsque la balle est perdue par un joueur et qu’il est l’intelligence artificielle alors le lancement de la balle se fait par le biais de la fonction game.ai.startBall().