Coder le jeu video html5 pong – Gestion du score et engagement

 

Neuvième partie consacrée au développement d’un jeu vidéo html5 javascript. 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

Avoir lu les tutoriaux suivants :
– 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;
– le 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 html5 pong – Renvoi de la balle;
– l’ajout du son lorsque la balle cogne un mur ou une raquette Coder le jeu vidéo Pong – Ajout du son;
– l’intelligence artificielle Coder le jeu video html5 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 est engagée immédiatement : le jeu démarre. Or il faut laisser le joueur mettre ou remettre en jeu la balle par le biais, par exemple, de 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, état indiquant que la balle n’est pas en jeu.

var 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 a été choisi d’utiliser soit la barre espace du clavier ou un clic droit souris.

Vous devez donc gérer :
– l’appui sur la barre espace du clavier;
– le clic droit souris.

Souvenez vous que les contrôles sont gérés dans le namespace javascript game.control.

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 du fait que sa position est modifiée par l’usage des variables directionX et directionY:

var 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.

Lorsque la touche est pressée, 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 touches 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.

var 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 donc faire en sorte que le joueur humain ou l’intelligence artificielle perdent 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;
  }
  ....
}
....

Cette fonction est à appeler 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 ayant aussi la charge des actions à réaliser lorsque la balle est perdue.

var 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 :

var main = function() {
  // le code du jeu
  game.clearLayer(game.playersBallLayer);
  game.movePlayers();
  game.displayPlayers();
  game.moveBall();
  game.lostBall();
  game.ai.move();
  game.collideBallWithPlayersAndAction();
  requestAnimId = window.requestAnimationFrame(main); // rappel de main au prochain rafraichissement de la page
}

En l’état, il ne passe rien lorsque la balle est perdue puisqu’aucune action n’a été indiquée.

Les actions à intégrer sont :
– l’incrément du score pour le joueur marquant 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 est incrémenté de 1 point;
– le joueur qui a perdu la balle fait la remise en jeu.

Définissez une nouvelle variable score dans chacun des objets playerOne et playerTwo.

var 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
  },

score est à incrémenter lorsque la balle est perdue, depuis la fonction lostBall du namespace javascript game :

var 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 est bien incrémenté. Par contre, cela n’est pas rafraichi à l’écran. Il suffit de le rajouter à la fin de la fonction en effaçant le score puis en le réaffichant à l’aide des fonctions dédiées définies dans un précédent article :

var 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 et donc ne permettrait pas au joueur de choisir l’endroit à partir duquel il engage la balle. Cette solution n’est donc 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 :

var 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();
  requestAnimId = window.requestAnimationFrame(main); // rappel de main au prochain rafraichissement de la page
}

La balle et le score s’arrêtent mais si vous appuyez sur la barre espace pour remettre en jeu la balle, vous constaterez 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 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 donc 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 que 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 laquelle des raquettes est intelligence artificielle et est chargée 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 laquelle des raquettes est contrôlée par l’IA permettrait de décider de la raquette qui engage la balle.

Il suffit d’ajouter 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).

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().

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.
Pour voir en live le code, cliquez sur Pong

 

Si vous avez aimé cet article, partagez le.

 

Si vous constatez des coquilles, ou avez des remarques à faire ou encore souhaitez manifester votre satisfaction de ce tuto, n’hésitez pas les commentaires sont faits pour ça.

 

Posté dans html5, pongTaggé développer pong, pong html5, pong javascript, tutoriel html5, tutoriel jeu vidéo, tutoriel pong  |  8 commentaires

8 réponses à "Coder le jeu video html5 pong – Gestion du score et engagement"

Répondre