Coder le jeu video html5 pong – Changement de look

 

Douzième partie consacrée au développement du jeu vidéo pong html5 javascript : le look actuel est oldschool. Je vous propose donc de le moderniser facilement.

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 Coder le jeu video html5 pong – Gestion du score et engagement;
– l’ajout d’un début et d’une fin à une partie Coder le jeu video html5 pong – début et fin de partie;
– la vitesse et la trajectoire de la balle Coder le jeu video html5 pong – vitesse et trajectoire de la balle.

 

Le principe

Jusqu’à présent, seuls des éléments graphiques simples ont été utilisés : des raquettes à base de rectangles blancs, une balle sous la forme d’un carré tout simple. A cela s’ajoute des polices de caractères par défaut.

Pour ce qui est du réagencement global, cela fera l’objet d’un autre article.

Vous allez changer cela en intégrant :
– des éléments graphiques à base d’images pour les raquettes et la balle : avec de vrais sprites;
– un décor digne de ce nom avec un agencement revu et corrigé;
– l’utilisation d’une police de caractères personnalisée pour l’affichage du score;
– la modification du bouton de démarrage du jeu.

Voici l’écran duquel on part :

 

Les changements

Voici les modifications à opérer pour un look modernisé :

1 – Les raquettes et la balle vont être remplacées par des images;
2 – le bouton start game va être remplacé par un bouton sous forme d’image;
3 – la police de caractères de l’affichage du score sera une police personnalisée.

 

Les raquettes et la balle

L’opération va consister ici à remplacer les rectangles blancs représentant raquettes et balle par des images. Libre à vous ensuite de relooker vos raquettes comme bon vous semble: il suffira de modifier l’image.

Je vous propose les 3 images suivantes pour respectivement le joueur 1, le joueur 2 et la balle :
  

La première chose à faire est de créer un dossier img au sein de l’arborescence projet dans lequel seront stockées les images :

Ensuite, vous devez associer ces 3 images à leurs objets respectifs: playerOne, playerTwo et ball du namespace javascript.

Une nouvelle propriété (imagePath) à ajouter à chaque objet dont la valeur est le chemin d’accès au fichier image.

var game = {
  ....
  ball : {
    width : 10,
    height : 10,
    color : "#FFD700",
    posX : 200,
    posY : 200,
    directionX : 1,
    directionY : 1,
    speed : 1,
    inGame : false,
    imagePath : "./img/ball.png"
    ....
  },

  playerOne : {
    width : 10,
    height : 50,
    color : "#FFFFFF",
    posX : 30,
    posY : 200,
    goUp : false,
    goDown : false,
    originalPosition : "left",
    score : 0,
    ai : false,
    imagePath : "./img/playerOne.png"
  },
    
  playerTwo : {
    width : 10,
    height : 50,
    color : "#FFFFFF",
    posX : 650,
    posY : 200,
    goUp : false,
    goDown : false,
    originalPosition : "right",
    score : 0,
    ai : true,
    imagePath : "./img/playerTwo.png"
  },
  ....
}

Ceci fait, vous devez afficher ces images en lieu et place des rectangles blancs pour les raquettes et du carré jaune pour la balle.

Pour les joueurs, une fonction du namespace game est dédiée à leur affichage:

var game = {
  ....
  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);
  },
  ....
}

Cette même fonction fait appel à la fonction drawRectangleInLayer du namespace game.display qui dessine un rectangle avec une couleur donnée à un endroit donné.

Ici plutôt que de dessiner des rectangles, il faut dessiner des images. D’où l’ajout d’une fonction de tracé d’image dans ce même namespace game.display.

game.display = {
  ....
  drawImageInLayer : function(targetLayer, image, x, y) {
    ....
  },
  ....
}

Mais afficher une image nécessite de disposer d’un objet Image: html5 permet de dessiner des images dans un canvas à partir de son contexte 2D via la fonction drawImage qui prend en paramètre un objet image.

Vous êtes donc obligé d’initialiser un objet Image à partir du chemin du fichier de l’image imagePath ajouté en propriété des objets playerOne, playerTwo et ball.

Dans l’ordre :
– ajouter une nouvelle propriété img à chacun des 3 objets;
– initialiser cet objet à partir du chemin du fichier imagePath.

Initialiser l’objet Image nécessite d’ajouter une nouvelle méthode initImage à chacun des 3 objets.

initImage : function(width, height) {
  this.img = new Image(width, height);
  this.img.src = this.imagePath;
  this.img.width = width;
  this.img.height = height;
}

N’oubliez pas de l’ajouter aux 3 objets playerOne, playerTwo et ball. Clairement, c’est une duplication de code et donc une mauvaise pratique.

Je ne souhaite pas partir dans du refactoring de code à cet instant car ça vous ferez sortir du sujet principal et du but, ça n’apporterait que confusion. Je vous propose de réaliser cette opération à la fin de cette partie.

Appelez la fonction initImage pour chacun des objets depuis la fonction init du namespace game:

var game = {
  ....
  init : function() {
    ....
    this.displayScore(0,0);
	
    this.ball.initImage(10,10);
    this.displayBall(200,200);
	
    this.playerOne.initImage(15,70);
    this.playerTwo.initImage(15,70);
    this.displayPlayers();
    ....
  },
  ....
}

Il reste à coder la fonction drawImageInLayer du namespace game.display:

game.display = {
  ....
  drawImageInLayer : function(targetLayer, image, x, y) {
    targetLayer.context2D.drawImage(image, x, y);
  }
  ....
}

Puis à modifier la fonction displayPlayers du namespace game:

var game = {
  ....
  displayPlayers : function() {
    game.display.drawImageInLayer(this.playersBallLayer, this.playerOne.img, this.playerOne.posX, this.playerOne.posY);
    game.display.drawImageInLayer(this.playersBallLayer, this.playerTwo.img, this.playerTwo.posX, this.playerTwo.posY);
  },
  ....
}

Même chose avec la fonction displayBall du namespace game:

var game = {
  ....
  displayBall : function() {
    game.display.drawImageInLayer(this.playersBallLayer, this.ball.img, this.ball.posX, this.ball.posY);
  },
  ....
}

Et voilà le résultat:

 

Le refactoring

Pour un petit rappel sur le modèle objet de javascript, je vous invite à lire ou relire les articles :
Les namespaces et objets javascript;
5 manières de créer des objets javascript.

Javascript a un modèle objet dit littéral différent du modèle objet par classe comme le langage java par exemple.

Que remarque-t-on à la relecture du code ? Non seulement qu’il y a des objets qui portent des propriétés identiques mais aussi que ces propriétés portent aussi le même sens.

A regarder de plus près, les objets playerOne, playerTwo et ball du namespace game, le partage d’un point de vue sens, des propriétés suivantes est flagrant:
– la largeur width;
– la hauteur height;
– la position sur l’axe X posX;
– la position sur l’axe Y posY;
– pour la représentation graphique, le chemin vers le fichier image imagePath et l’objet image img.

Regrouper ces propriétés dans un objet dédié a du sens: ça ressemble presque à des propriétés de sprite.

Où placer cet objet au sein des différents namespace javascript ? Vous vous êtes attachés à créer du code qui soit réutilisable avec notamment le namespace game.display.

La bonne idée serait de continuer cette volonté de réutilisation. Le sprite étant un élément graphique, le placer dans le namespace game.display n’est pas déconnant.

Ce qui donne dans le namespace game.display:

game.display = {
  ....
  sprite : {
    width : 0,
    height : 0,
    posX : null,
    posY : null,
    imagePath : "",
    img : null
  },

  createSprite : function(width, height, posX, posY, imagePath) {
    var sprite = Object.create(this.sprite);
   
    sprite.width = width;
    sprite.height = height;
    sprite.posX = posX;
    sprite.posY = posY;
    sprite.imagePath = imagePath;
    sprite.img = new Image();
    sprite.img.src = imagePath;
    sprite.img.width = width;
    sprite.img.height = height;

    return sprite;
  }
}

L’objet sprite avec ses propriétés est ajouté ainsi qu’un cloneur de cet objet (createSprite).

Ensuite substituez un sprite aux propriétés communes dans les objets playerOne, playerTwo et ball, les propriétés width, height, posX, posY, imagePath et img disparaissent au profit de l’objet sprite.

Supprimez aussi la méthode initImage dont le contenu a été transféré dans le cloneur createSprite:

var game = {
  ....
  ball : {
    sprite : null,
    color : "#FFD700",
    directionX : 1,
    directionY : 1,
    speed : 1,
    inGame : false,
    ....
  },
  ....
  playerOne : {
    sprite : null,
    color : "#FFFFFF",
    goUp : false,
    goDown : false,
    originalPosition : "left",
    score : 0,
    ai : false
  },
    
  playerTwo : {
    sprite : null,
    color : "#FFFFFF",
    goUp : false,
    goDown : false,
    originalPosition : "right",
    score : 0,
    ai : true
  },
}

Les propriétés appartenant désormais à l’objet sprite encapsulé dans les objets playerOne, playerTwo et ball, il ne reste plus qu’à modifier le code où les propriétés width, height, posX, posY sont utilisées en les substituant respectivement à sprite.width, sprite.height, sprite.posX, sprite.posY.

Certes, un peu fastidieux, mais c’est le prix de la qualité et de la réutilisabilité.

 

Le bouton démarrage du jeu

Le bouton très moche startGame est à remplacer par l’image suivante :

La première chose à faire est de remplacer le bouton par l’image:

<input id="startGame" type="image" src="./img/startBtn.png">

Tel quel, l’image est à sa taille originelle, il faut donc la redimensionner comme ceci:

<input id="startGame" style="width:120px" type="image" src="./img/startBtn.png">

Et voilà le résultat:

Et il n’y a que ça à faire.

 

La police de caractères du score

L’objectif est d’intégrer une police de caractères personnalisée pour l’affichage du score.

La police choisie pour l’exemple :

Vous pouvez la télécharger en cliquant DS_DIGITAL. Il suffit ensuite de décompresser le fichier et de copier le dossier décompressé dans le dossier font du projet à créer s’il n’existe pas.

Pour l’utiliser, il est indispensable de la déclarer par le biais du mot clé css @font-face comme suit :

<style>
@font-face { 
  font-family: 'DS-DIGIB'; 
  src: url('./font/ds_digital/DS-DIGIB.TTF'); 
}
</style>

où :
font-family donne un nom d’usage à utiliser dans le jeu vidéo Pong;
src indique où se trouve le fichier ttf de la police de caractères.

Pour utiliser cette police, il suffit de la spécifier DS-DIGIB dans la propriété font-family du style :

font-family: DS-DIGIB;

C’est au niveau du contexte du canvas html5 qu’il va falloir opérer par le biais de la propriété font en remplaçant dans la fonction displayScore:

var game = {
  ....
  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);
  },
  ....
}

par

var game = {
  ....
  displayScore : function(scorePlayer1, scorePlayer2) {
    game.display.drawTextInLayer(this.scoreLayer, scorePlayer1, "50pt DS-DIGIB", "#FFFFFF", this.scorePosPlayer1, 55);
    game.display.drawTextInLayer(this.scoreLayer, scorePlayer2, "50pt DS-DIGIB", "#FFFFFF", this.scorePosPlayer2, 55);
  },
  ....
}

Et vous obtenez :

 
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é canvas html5, canvas image, jeu video html5, tuto html5  |  Laisser un commentaire

Répondre