Coder le jeu vidéo pong html5 – Raquettes et balle




 

La saga Pong javascript html5 continue….Dans cet épisode, nous allons terminer la mise en place de l’environnement du jeu à proprement parler avant de donner vie au jeu en animant le tout : les raquettes et la balle, ainsi que le score.


 

La série

Pour ceux qui souhaiteraient aller plus vite :
– animation de la balle : ici;
– animation de la raquette avec le clavier : ici.

 

Prérequis

Avoir lu l’épisode 1 de ce tuto.

 

Rappel de l’épisode 1

Souvenez vous, il a été créé dans l’épisode 1, un objet Layer (comparables aux layers flash) dédié au terrain : un fond noir, et un filet.

Deux autres doivent être maintenant créés :
– un dédié à l’affichage du score;
– le dernier dédié à l’animation des raquettes et de la balle.

 

Les 3 layers

Tout comme pour le terrain, nous créons :
– un layer (canvas html5) dédié pour le score;
– et un layer (canvas html5) dédié pour les raquettes et la balle.

Pourquoi utiliser 3 layer au lieu d’un seul ?
– la première raison est l’optimisation : avec un seul layer, il va falloir rafraîchir tous les éléments du jeu que sont les raquettes, la balle, le score, et le filet à chaque rafraîchissement;
– la seconde raison est de l’ordre purement conceptuel : on regroupe des éléments de même nature dans un layer dédié.

Les layer sont superposables par le biais de la propriété zIndex. Il est donc permis de superposer nos 3 layer html5 : il reste à déterminer dans quel ordre. Un layer avec un zIndex sera toujours placé devant un layer html5 avec un zIndex plus faible.

Le layer html5 terrain n’a pas vocation à changer : aucune animation. Il semble être de bon sens de le placer dans la couche la plus basse, en dessous de toutes les autres.

Le layer html5 dédié aux raquettes et à la balle sera en perpétuel changement du fait de l’animation des raquettes et de la balle.

Le dernier layer html5 dédié au score changera uniquement lorsqu’il y aura perte de balle.

Pour résumer :
– layer terrain : le plus bas;
– layer raquettes : le plus haut;
– layer score : entre les 2 précédents.

Les 3 layer html5 ont les mêmes dimensions. Toutefois et pour éviter le rafraîchissement perpétuel du terrain et du score, les 2 layers supérieurs (dédiés aux raquettes et au score) sont transparents de manière à laisser apparaître le layer terrain.

La création des 3 layer html5 dans la fonction init du namespace javascript game :

.....
  groundColor : "#000000",
  netColor : "#FFFFFF",
  groundLayer : null,  
  scoreLayer : null,
  playersBallLayer : null,

  init : function() {
    this.groundLayer = game.display.createLayer("terrain", this.groundWidth, this.groundHeight, undefined, 0, "#000000", 0, 0);
    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, undefined, 1, undefined, 0, 0);
    this.playersBallLayer = game.display.createLayer("joueursetballe", this.groundWidth, this.groundHeight, undefined, 2, undefined, 0, 0);	
  }
.....

A l’exécution, les 2 nouveaux layer html5 sont invisibles. Pour voir qu’ils sont bien créés et existent, ajoutons une fonction d’affichage de texte au namespace javascript game.display :

.....
  drawTextInLayer : function(targetLayer, text, font, color, x, y) {
    targetLayer.context2D.font = font;
    targetLayer.context2D.fillStyle = color;
    targetLayer.context2D.fillText(text, x, y);
  }
.....

Puis appelons les pour afficher du texte en rouge dans les 2 nouveaux layer html5 :

.....
init : function() {
  this.groundLayer = game.display.createLayer("terrain", this.groundWidth, this.groundHeight, undefined, 0, "#000000", 0, 0); 

  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, undefined, 1, undefined, 0, 0);

  game.display.drawTextInLayer(this.scoreLayer, "SCORE", "10px Arial", "#FF0000", 10, 10);
  this.playersBallLayer = game.display.createLayer("joueursetballe", this.groundWidth, this.groundHeight, undefined, 2, undefined, 0, 0);	

  game.display.drawTextInLayer(this.playersBallLayer, "JOUEURSETBALLE", "10px Arial", "#FF0000", 100, 100);
}
.....

Voici ce que ça donne à l’exécution :
pong_base_layer

 

L’affichage du score

Pour assurer l’affichage du score, nous réutilisons la fonction drawTextInLayer créée précédemment.

Toutefois, pour des raisons de clarté du code et en faciliter la relecture, la fonction est encapsulée dans une autre dédiée au jeu vidéo html5 pong avec un nom très parlant.

Ceci engendre une spécificité propre au jeu vidéo html5 pong et donc non réutilisable.
En conséquence, nous l’intégrons dans le namespace game.

Cette fonction prend 2 paramètres que sont les scores des 2 joueurs.

var game = {
.....
  displayScore : function(scorePlayer1, scorePlayer2) {
  }
}

Puis elle affiche les scores au centre sur le haut du terrain en n’oubliant pas de cibler le bon layer html5. Il faut au préalable déterminer les positions des 2 scores.

La position du score du joueur 1 est à 300 pixels du bord gauche du canvas html5.
La position du score du joueur 2 est à 365 pixels du bord gauche du canvas html5.

Nous définissons 2 nouvelles variables dédiées dans le namespace game.

var game = {
.....
  scorePosPlayer1 : 300,
  scorePosPlayer2 : 365,
.....
  displayScore : function(scorePlayer1, scorePlayer2) {
    game.display.drawTextInLayer(this.scoreLayer, scorePlayer1, "60px Arial", "#FFFFFF", this.scorePosPlayer1, 55);
    game.display.drawTextInLayer(this.scoreLayer, scorePlayer2, "60px Arial", "#FFFFFF", this.scorePosPlayer2, 55);
  }
}

Voici ce que ça donne à l’exécution :
pong_base_layer_2

 

Le tracé des raquettes et de la balle

Au même titre que le score, nous ajoutons 2 fonctions dédiées respectivement à l’affichage de la balle et des raquettes :
– displayBall pour la balle;
– displayPlayers pour les raquettes.

Fonctions dédiées donc à ajouter au namespace javascript game.

Pour la balle, nous avons besoin de définir :
– sa taille en pixels (longueur et largeur);
– sa couleur;
– et sa position (coordonnées abscisse (x) et ordonnée (y)).

Nous ajoutons donc autant de propriétés au namespace javascript game.

Encore mieux, nous encapsulons ces propriétés dans un objet ball :

var game = {
.....
  ball : {
    width : 10,
    height : 10,
    color : "#FFFFFF",
    posX : 200,
    posY : 200
  },

  init : function() {
.....

Il reste à la dessiner avec un simple carré qui prendra les dimensions et couleur définis dans l’objet. Nous réutilisons la fonction drawRectangleInLayer que nous avons définie dans l’article précédent.

  displayBall : function() {
    game.display.drawRectangleInLayer(this.playersBallLayer, this.ball.width, this.ball.height, this.ball.color, this.ball.posX, this.ball.posY);
  } 

Il ne reste plus qu’à l’appeler depuis la fonction init pour voir le résultat :

.....
  init : function() {
.....
    this.displayScore(0,0);
    this.displayBall(200,200);
  },
......

Le résultat :

pong_base_layer_3

Passons aux raquettes pour lesquelles nous avons besoin de définir :
– la taille en pixels (longueur et largeur);
– la couleur;
– la position (coordonnées abscisse (x) et ordonnée (y)).

Reprenons la même méthode, à savoir un objet dédié pour chacune des raquettes :

var game = {
.....
  playerOne : {
    width : 10,
    height : 10,
    color : "#FFFFFF",
    posX : 10,
    posY : 200
  },
  
  playerTwo : {
    width : 10,
    height : 10,
    color : "#FFFFFF",
    posX : 600,
    posY : 200
  },

  init : function() {
.....

Très proche de l’objet ball, alors pourquoi un deux objets supplémentaires identiques. Simple et bonne raison : une sémantique permettant une relecture facile du code.

Nous définissons ensuite la fonction d’affichage des raquettes :

  displayPlayers : function() {
    game.display.drawRectangleInLayer(this.playersBallLayer, this.playerOne.width, this.playerOne.height, this.playerOne.color, this.playerOne.posX, this.playerOne.posY);
    game.display.drawRectangleInLayer(this.playersBallLayer, this.playerTwo.width, this.playerTwo.height, this.playerTwo.color, this.playerTwo.posX, this.playerTwo.posY);
  } 

Et l’appelons depuis la fonction init :

.....  
  init : function() {
    this.terrainLayer= game.display.createLayer("terrain", this.groundWidth, this.groundHeight, undefined, 0, "#000000", 0, 0); 
    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, undefined, 1, undefined, 0, 0);
    game.display.drawTextInLayer(this.scoreLayer , "SCORE", "10px Arial", "#FF0000", 10, 10);
	
    this.playersBallLayer = game.display.createLayer("joueursetballe", this.groundWidth, this.groundHeight, undefined, 2, undefined, 0, 0);	
    game.display.drawTextInLayer(this.playersBallLayer, "JOUEURSETBALLE", "10px Arial", "#FF0000", 100, 100);
	
    this.displayScore(0,0);
    this.displayBall();
    this.displayPlayers();
  },
.....  

Le résultat :

pong_base_layer_4

Pour voir en live le code, cliquez sur Pong

La prochaine étape de cette série va consister à faire animer la balle en la faisant rebondir sur les murs à suivre ici.

 

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.

 
Le source javascript complet :
game.html

<html>
 <body>
 </body>
<script src="game.js"></script>
<script src="game.display.js"></script>
<script>
(function () {
  // début du code isolé
  var requestAnimId;
 
  var initialisation = function() {
    // le code de l'initialisation
    game.init();
    requestAnimId = window.requestAnimationFrame(main); // premier appel de main au rafraichissement de la page
  }
 
  var main = function() {
    // le code du jeu
    requestAnimId = window.requestAnimationFrame(main); // rappel de main au prochain rafraichissement de la page
  }
 
  window.onload = initialisation; // appel de la fonction initialisation au chargement de la page
 
  // fin du code isolé
})();
</script> 
</html>

game.js

var game = {
 
  groundWidth : 700,
  groundHeight : 400,
  netWidth : 6,
  groundColor : "#000000",
  netColor : "#FFFFFF",
 
  groundLayer : null,  
  scoreLayer : null,
  playersBallLayer : null,
   
  scorePosPlayer1 : 300,
  scorePosPlayer2 : 365,
  
  ball : {
    width : 10,
    height : 10,
    color : "#FFFFFF",
    posX : 200,
    posY : 200
  },
  
  playerOne : {
    width : 10,
    height : 50,
    color : "#FFFFFF",
    posX : 30,
    posY : 200
  },
  
  playerTwo : {
    width : 10,
    height : 50,
    color : "#FFFFFF",
    posX : 650,
    posY : 200
  },
  
  init : function() {
    this.groundLayer = game.display.createLayer("terrain", this.groundWidth, this.groundHeight, undefined, 0, "#000000", 0, 0); 
    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, undefined, 1, undefined, 0, 0);
    game.display.drawTextInLayer(this.scoreLayer , "SCORE", "10px Arial", "#FF0000", 10, 10);
	
    this.playersBallLayer = game.display.createLayer("joueursetballe", this.groundWidth, this.groundHeight, undefined, 2, undefined, 0, 0);	
    game.display.drawTextInLayer(this.playersBallLayer, "JOUEURSETBALLE", "10px Arial", "#FF0000", 100, 100);
	
    this.displayScore(0,0);
    this.displayBall(200,200);
    this.displayPlayers();
  },
  
  displayScore : function(scorePlayer1, scorePlayer2) {
    game.display.drawTextInLayer(this.scoreLayer, scorePlayer1, "60px Arial", "#FFFFFF", this.scorePosPlayer1, 55);
    game.display.drawTextInLayer(this.scoreLayer, scorePlayer2, "60px Arial", "#FFFFFF", this.scorePosPlayer2, 55);
  },
  
  displayBall : function() {
    game.display.drawRectangleInLayer(this.playersBallLayer, this.ball.width, this.ball.height, this.ball.color, this.ball.posX, this.ball.posY);
  },
  
  displayPlayers : function() {
    game.display.drawRectangleInLayer(this.playersBallLayer, this.playerOne.width, this.playerOne.height, this.playerOne.color, this.playerOne.posX, this.playerOne.posY);
    game.display.drawRectangleInLayer(this.playersBallLayer, this.playerTwo.width, this.playerTwo.height, this.playerTwo.color, this.playerTwo.posX, this.playerTwo.posY);
  } 
 
};

game.display.js

game.display = {
  container : "",
 
  layer : {
    name : "",
    canvas : "",
    context2D : "",
    posX : null,
    posY : null,
    width : "",
    height : "",
    backgroundColor : "",
    zIndex : ""
  },
 
  createLayer : function(name, width, height, htmlContainer , zIndex, backgroundColor, x, y) {
    var layer = Object.create(this.layer);
 
    layer.canvas = window.document.createElement("canvas");
 
    layer.canvas.id = name;
 
    if ( backgroundColor != undefined )
      layer.canvas.style.background = backgroundColor;
 
    layer.zIndex = zIndex
    layer.canvas.style.zIndex = zIndex;
 
    layer.width = width
    layer.canvas.width = width;
 
    layer.height = height
    layer.canvas.height = height;
 
    if ( x != undefined )
      layer.posX = x;
 
    if ( y != undefined )
      layer.posY = y;
 
    layer.canvas.style.position = "absolute";
	
    if ( x != undefined )
      layer.canvas.style.left = x;
    if ( y != undefined )
      layer.canvas.style.top = y;
    
    if ( htmlContainer != undefined ) {
      htmlContainer.appendChild(layer.canvas);
    } else {
	  document.body.appendChild(layer.canvas);
    }

    layer.context2D = layer.canvas.getContext('2d');
    return layer;
  },

  drawRectangleInLayer : function(targetLayer, width, heigth, color, x, y) {
    targetLayer.context2D.fillStyle = color;
    targetLayer.context2D.fillRect (x, y, width, heigth);
  },

  drawTextInLayer : function(targetLayer, text, font, color, x, y) {
    targetLayer.context2D.font = font;
    targetLayer.context2D.fillStyle = color;
    targetLayer.context2D.fillText(text, x, y);
  }
}

 

Posté dans html5, pongTaggé jeu canvas html5, pong html5, pong javascript, tutoriel html5, tutoriel jeu vidéo  |  29 commentaires

29 réponses à "Coder le jeu vidéo pong html5 – Raquettes et balle"

Répondre

*