Pong en javascript – adaptation à la taille de l’écran

Jusqu’à présent, le jeu Pong html5 que vous avez développé fonctionne sur PC. Toutefois, un problème de dimensionnement se pose puisque les dimensions des objets du jeu tel que la taille des boutons, de l’écran sont spécifiés de manière absolue. Je vous propose donc de gérer la taille des objets du jeu en les adaptant à l’écran du PC sur lequel le jeu vidéo est exécuté.

 

Prérequis pour le jeu Pong html5

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. La gestion des parties Coder le jeu vidéo Pong – Début et fin de partie. La gestion de la vitesse de la balle Coder le jeu vidéo Pong – Vitesse et trajectoire. La version modernisée Coder le jeu vidéo Pong – Graphisme moderne.

 

Introduction au jeu Pong html5

Je ne parlerais pas des Media Queries CSS3. L’idée ici est d’adapter proportionnellement l’écran de jeu à la taille du PC sur lequel le jeu est exécuté. Pas de changement de disposition, et une seule feuille de style quelque soit le média utilisé. Il faut pondérer toutes les positions et mesures des objets graphiques en fonction de la taille de l’écran.

 

Qu’est ce qui change par rapport à la version originelle ?

D’un point de vue graphique, seules les dimensions changent. En passant d’une taille d’écran à une autre ou d’une résolution à une autre, rien ne change. Hormis le fait que les éléments du jeu ont une taille adaptée à l’écran.

 

Les dimensions et les positions s’adaptent au média

L’idée est de créer un ratio qui servira depuis une résolution de développement à passer à une résolution cible.

Le calcul de ce ratio se fait à partir de la taille de l’écran cible et la taille de l’écran qui a servi au développement du jeu vidéo.

Cette méthode, facile à comprendre et à implémenter, a le privilège de fonctionner partout et tout le temps.

Veillez bien à retenir que les objets et les positions seront redimensionnés. La difficulté ici est de n’oublier personne. Sinon l’affichage du jeu et son comportement risque fortement d’être chaotique.

 

Enumération des valeurs à repositionner

Oui, énumérer il faut. Non il ne faut pas appliquer manuellement une opération de redimensionnement sur chaque élément énuméré.

Le jeu, si c’en est un, consiste à relever tous les éléments graphiques du jeu avec leur valeur de taille et de positionnement. De cette liste va naitre un namespace javascript de configuration game.conf.

Vous avez entre autres la taille du terrain (les 3 layers), la taille et la position des joueurs, de la balle, du filet, du score.

Vient ensuite la création d’une méthode dédiée. Celle ci appliquera aux propriétés du namespace javascrip1t game.conf le redimensionnement voulu.

Les changements

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

const conf = {
   GROUNDLAYERWIDTH : 700,
   GROUNDLAYERHEIGHT : 400,
    
   SCORELAYERWIDTH : 700,
   SCORELAYERHEIGHT: 400,
 
   PLAYERSBALLLAYERWIDTH : 700,
   PLAYERSBALLLAYERHEIGHT: 400,
    
   NETWIDTH : 6,
    
   SCOREPOSXPLAYER1 : 300,
   SCOREPOSYPLAYER1 : 55,
   SCOREPOSXPLAYER2 : 365,
   SCOREPOSYPLAYER2 : 55,
   SCOREFONTSIZE : 50,
    
   BALLWIDTH : 10,
   BALLHEIGHT : 10,
   BALLPOSX : 200,
   BALLPOSY : 200,
 
   PLAYERONEWIDTH : 10,
   PLAYERONEHEIGHT : 70,
   PLAYERONEPOSX : 30,
   PLAYERONEPOSY : 200,
 
   PLAYERTWOWIDTH : 10,
   PLAYERTWOHEIGHT : 70,
   PLAYERTWOPOSX : 650,
   PLAYERTWOPOSY : 200
    
}

Ceci réalisé, il vous faut substituer toutes ces constantes aux valeurs correspondantes dans le code. Principalement dans le namespace javascript game, là où toutes les initialisations se font.

Du fait du basculement de certaines variables dans le namespace javascript conf, celles-ci disparaissent du namespace javascript game.

Supprimer le code suivant du namespace game.

scorePosPlayer1 : 300,
scorePosPlayer2 : 365,

Les variables  portent la position des scores sur l’axe X de l’écran de jeu et sont remplacées par les valeurs du namespace javascript conf.

SCOREPOSXPLAYER1 : 300,
SCOREPOSYPLAYER1 : 55,
SCOREPOSXPLAYER2 : 365,
SCOREPOSYPLAYER2 : 55,

Hé oui, il n’y a pas deux mais quatre valeurs. Les positions sur la verticale ont été rajoutées car ells étaient en dur dans le code (mauvaise pratique) SCOREPOSYPLAYER1, SCOREPOSYPLAYER2. A aussi été rajoutée la taille de la font du score SCOREFONTSIZE.

Enfin les valeurs game.groundWidth, game.groundHeight, game.netWidth sont initialisées dans le namespace conf. Elles sont redimensionnées dans ce même namespace javascript, et sont de fait déportées. Il n’y a donc aucun intérêt à les conserver dans le namespace game. S’en suivent les changements suivant au sein du namespace game.

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.

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.

const game = {
  ....   
  ball : {
    ....   
    bounce : function(soundToPlay) {
      if ( this.sprite.posX > conf.GROUNDLAYERWIDTH || this.sprite.posX < 0 ) {
        this.directionX = -this.directionX;
    soundToPlay.play();
    }
      if ( this.sprite.posY > conf.GROUNDLAYERHEIGHT || this.sprite.posY < 0  ) {
        this.directionY = -this.directionY;
        soundToPlay.play();
      }    
    },
  ....
  },
  .... 
  init : function() {
    ....  
    this.groundLayer= game.display.createLayer("terrain", conf.GROUNDLAYERWIDTH, conf.GROUNDLAYERHEIGHT, this.divGame, 0, "#000000", 10, 50); 
    game.display.drawRectangleInLayer(this.groundLayer, conf.NETWIDTH, conf.GROUNDLAYERHEIGHT, this.netColor, conf.GROUNDLAYERWIDTH/2 - conf.NETWIDTH/2, 0);
     
    this.scoreLayer = game.display.createLayer("score", conf.GROUNDLAYERWIDTH, conf.GROUNDLAYERHEIGHT, this.divGame, 1, undefined, 10, 50);
    game.display.drawTextInLayer(this.scoreLayer , "SCORE", "10px Arial", "#FF0000", 10, 10);
     
    this.playersBallLayer = game.display.createLayer("joueursetballe", conf.GROUNDLAYERWIDTH, conf.GROUNDLAYERHEIGHT, this.divGame, 2, undefined, 10, 50);  
    game.display.drawTextInLayer(this.playersBallLayer, "JOUEURSETBALLE", "10px Arial", "#FF0000", 100, 100);
     
    this.displayScore(0,0);
   
    this.ball.sprite = game.display.createSprite(conf.BALLWIDTH,conf.BALLHEIGHT,conf.BALLPOSX,conf.BALLPOSY,"./img/ball.png");
    this.displayBall();
   
    this.playerOne.sprite = game.display.createSprite(conf.PLAYERONEWIDTH,conf.PLAYERONEHEIGHT,conf.PLAYERONEPOSX,conf.PLAYERONEPOSY,"./img/playerOne.png");
    this.playerTwo.sprite = game.display.createSprite(conf.PLAYERTWOWIDTH,conf.PLAYERTWOHEIGHT,conf.PLAYERTWOPOSX,conf.PLAYERTWOPOSY,"./img/playerTwo.png");  
    this.displayPlayers();
    ....   
  },
     
  displayScore : function(scorePlayer1, scorePlayer2) {
    game.display.drawTextInLayer(this.scoreLayer, scorePlayer1, conf.SCOREFONTSIZE + "pt DS-DIGIB", "#FFFFFF", conf.SCOREPOSXPLAYER1, conf.SCOREPOSYPLAYER1);
    game.display.drawTextInLayer(this.scoreLayer, scorePlayer2, conf.SCOREFONTSIZE + "pt DS-DIGIB", "#FFFFFF", conf.SCOREPOSXPLAYER2, conf.SCOREPOSYPLAYER2);
  },
  
  ....   
};

Ce dont vous avez besoin pour redimensionner

D’un point de vue plus opérationnelle, vous avez besoin de 5 nouvelles propriétés à rattacher au namespace javascript game.
– les deux premières pour stocker la résolution de l’écran qui a servi au développement;
– les deux suivantes pour stocker la résolution de l’écran cible qui servira à jouer;
– les deux dernières pour stocker le ration qui permettra de passer de la résolution de développement à la résolution cible sur chacun des axes.

Pour les puristes, l’usage d’une structure de données pour chaque couple de valeurs peut aussi être un usage.

const game = {
  ....
  devResX : null,
  devResY : null,
  targeResX : null,
  targetResY : null,
  ratioResX : null,
  ratioResY : null,
  ....
}

Il faut ensuite fixer ces valeurs: les deux premières sont fixées par le développeur et sont relatives à son environnement. Les quatre dernières sont calculées.

const game = {
  ....
  devResX : 1366,
  devResY : 738,
  ....
}

Pour le calcul des quatre dernières valeurs. Une fonction dédiée qui, à partir de la résolution du joueur, calculera un ratio de transformation.

const game = {
  ....
  initScreenRes : function() {
    this.targetResX = window.screen.availWidth;
    this.targetResY = window.screen.availHeight;
    this.ratioResX = this.targetResX/this.devResX;
    this.ratioResY = this.targetResY/this.devResY;
  },
  ....
}

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

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

La fonction de redimensionnement

Une seule fonction qui balaie toutes les données du namespace conf pour leur appliquer les ratios.

Il y a un ratio à appliquer sur les données relatives à l’horizontale X et un ratio à appliquer sur les données relatives à la verticale Y.

Pour appliquer avec discernement ces ratios, la fonction scrute le nom de la propriété et:
– regarde si elle contient X ou WIDTH (les majuscules sont importantes) pour appliquer ratioX;
– regarde si elle contient Y ou HEIGHT(les majuscules sont importantes) pour appliquer ratioX.

Cette règle impose donc une contrainte sur le nommage des propriétés relatives à l’affichage et intégrées au namespace conf.

const game = {
  ....
  resizeDisplayData : function(object, ratioX, ratioY) {
    var property;
    for ( property in object ) { 
      if ( property.match(/^.*X.*$/i) || property.match(/^.*WIDTH.*$/i) ) {
        object[property] = Math.round(object[property] * ratioX);
      } else {
        object[property] = Math.round(object[property] * ratioY);
      }
    }
  },
  ....
}

Mise en opération du redimensionnement

Ici les choses sont simples, puisqu’il suffit de référencer le namespace conf dans le fichier pong.htm

<html>
 <body>
 </body>
<style>
@font-face { 
  font-family: 'DS-DIGIB'; 
  src: url('./font/ds_digital/DS-DIGIB.TTF'); 
}
</style>
<script src="conf.js"></script>
<script src="game.js"></script>
....
</html>

Puis depuis la fonction init namespace game:
– d’appeler la fonction d’initialisation des ratios;
– et d’appeler la fonction d’application des ratios.

const game = {
  ....
  init : function() {
    this.initScreenRes();
    this.resizeDisplayData(conf,this.ratioResX,this.ratioResY);
    ....
  },
  ....
}

Mais ce n’est pas tout. En l’état, balle et raquettes gardent leurs tailles originelles.

Du fait de l’implémentation de la fonction drawImageInLayer du namespace game.display. Elle ne prend pas en compte une taille cible pour les images affichées.

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

Il suffit de la modifier en changeant la signature de la fonction (deux paramètres supplémentaires) et en appelant la fonction drawImage avec la signature comportant une taille cible et le tour est joué.

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

Explore l'univers de la création de jeux vidéo

Saisis ton prénom et ton e-mail pour recevoir ton livre GRATUIT et commence ton voyage ludique dès maintenant.

Bravo, jette un œil à ta boite mail pour télécharger ton guide.