Coder le jeu vidéo pong html5 – Animer la balle

 

Troisième partie de cette série d’articles consacrée au développement d’un jeu vidéo html5 javascript. L’objectif du jour est d’animer la balle en la faisant rebondir sur les murs latéraux du terrain de jeu.


 

La série

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

 

Prérequis

Avoir lu les tutoriaux consacrés à l’initialisation du projet Coder le jeu vidéo Pong 1ere partie, à 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.

 

Le principe

Pour donner un mouvement à la balle, il va falloir faire évoluer ses coordonnées :

Pour un mouvement horizontal vers la droite de l’écran, seule la position sur l’axe des abscisses est incrémentée.

Pour un mouvement horizontal vers la gauche de l’écran, la position sur l’axe des abscisses est décrémentée.

Le principe est le même pour un mouvement vertical, à la différence que seule la position sur l’axe des ordonnées est incrémentée ou décrémentée.

Pour un mouvement ni horizontal, ni vertical, c’est la position sur les deux axes qui évolue.

 

Le code

L’objet ball défini dans l’article précédent comporte une série de propriétés dont la position matérialisée par les 2 variables posX et posY :

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

Ce sont les variations des valeurs des 2 propriétés posX et posY qui permettront de bouger la balle sur le terrain.

Qui dit mouvement, dit vitesse.

Du coup, ajoutons une nouvelle propriété relative à la vitesse de la balle.

var game = {
.....
  ball : {
    width : 10,
    height : 10,
    color : "#FFFFFF",
    posX : 200,
    posY : 200,
    speed : 1
  },
 
  init : function() {
.....

La valeur de la vitesse correspond à un incrément en pixels de la position de la balle, incrément appliqué à chaque rafraichissement de l’affichage.

Plus la valeur de cette propriété sera grande, plus la balle paraitra se déplacer vite. Cela peut être un des moyens pour jouer sur la difficulté du jeu.

Cependant, en faisant des tests sur la valeur de cette variable, on remarque un inconfort visuel avec des valeurs trop élevées.

La valeur 1 donnée à la propriété va faire que la balle va se déplacer vers le bas et la droite de l’écran de jeu.

Il reste à créer une méthode ou fonction dédiée au mouvement de la balle dans l’objet ball.

Cette fonction va faire varier la position de la balle par le biais de deux nouvelles variables directionX et directionY.

Les propriétés directionX et directionY ne prennent chacune que 2 valeurs relatives au sens de déplacement sur les axes X (abscisses) et Y (ordonnées).

Pour un déplacement vers la droite de l’écran sur l’axe X, directionX vaudra 1.
Pour un déplacement vers la gauche de l’écran sur l’axe X, directionX vaudra -1.
Pour un déplacement vers le bas de l’écran sur l’axe Y, directionY vaudra 1.
Pour un déplacement vers la haut de l’écran sur l’axe Y, directionY vaudra -1.

var game = {
...
  ball : {
    ...
    move : function() {
      this.posX += this.directionX * this.speed;
      this.posY += this.directionY * this.speed;
    },
  }
...
}

Ce n’est toutefois pas suffisant puisqu’il faut rafraichir l’affichage de la balle lorsque sa position change. Simple, il n’y a qu’à rappeler la fonction displayBall existante.

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

Pour rendre la relecture du code plus simple, encapsulons l’appel de ces 2 fonctions dans une autre que nous appelons moveBall et que nous rattachons au namespace javascript game.js :

var game = {
  ...
  moveBall : function() { 
    this.ball.move();
    this.displayBall();
  }, 
  ...
}

Si nous gardons la fonction telle qu’elle est, la balle va se déplacer vers le bas et la droite de l’écran de jeu puis va disparaitre de l’écran de jeu. Il va donc falloir la faire rebondir sur les bords de l’écran.

Le principe pour les rebonds est simple :
– lorsque la position de la balle atteint le bord droit de l’écran (en d’autres termes si sa valeur dépasse la longueur de l’écran de jeu), on inverse sa direction (la valeur de directionX) doit être inversée;
– même chose si la position de la balle atteint le bord gauche de l’écran (en d’autres termes si sa valeur est inférieure à 0);
– lorsque la position de la balle atteint le bas de l’écran (en d’autres termes si sa valeur dépasse la largeur de l’écran de jeu), on inverse sa direction (la valeur directionY) doit être inversée;
– même chose si la position de la balle atteint le haut de l’écran (en d’autres termes si sa valeur est inférieure à 0).

Au final, le code de la fonction bounce rattachée à l’objet ball :

var game = {
...
  ball : {
    ...
    bounce : function() {
      if ( this.posX > game.groundWidth || this.posX < 0 )
        this.directionX = -this.directionX;
      if ( this.posY > game.groundHeight || this.posY < 0  )
        this.directionY = -this.directionY;			
    },
  }
...
}

Nous intégrons l’appel dans la fonction moveBall :

var game = {
...
  moveBall : function() { 
    this.ball.move();
    this.ball.bounce();
    this.displayBall();
  }, 
...

Puis à appeler la fonction moveBall depuis la boucle main d’exécution main :

...
  var main = function() {
    // le code du jeu
    game.moveBall();
    requestAnimId = window.requestAnimationFrame(main); // rappel de main au prochain rafraîchissement de la page
  }
...

Il reste une petite chose à régler, si nous exécutons le code en l’état, la balle laisse une traînée derrière elle.

La traînée est liée à l’absence de rafraîchissement de l’affichage. Le rafraîchissement consiste simplement à effacer le contenu du layer où sont affichés balle et joueurs avant le réaffichage de l’un ou l’autre

Nous ajoutons une méthode d’effacement à l’objet layer :

...
  layer : {
    name : "",
    canvas : "",
    context2D : "",
    posX : null,
    posY : null,
    width : "",
    height : "",
    backgroundColor : "",
    zIndex : "",
	
    clear : function() {
      this.context2D.clearRect(0, 0, this.canvas.width, this.canvas.height);
    }
  },
...

Puis nous créons une fonction d’effacement de layer dans le namespace game :

var game = {
...
  clearLayer : function(targetLayer) {
    targetLayer.clear();
  }
...

Enfin, nous appelons la fonction d’effacement du namespace game depuis la boucle principale (main) du jeu :

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

La balle bouge mais le résultat reste imparfait puisque les raquettes ont disparu :

Les raquettes et la balle étant dessinées dans le même layer, la fonction clearLayer va effacer la balle mais aussi les raquettes.
Il faut donc réafficher les raquettes à chaque fois puisque elles sont sur le même layer que la balle, en utilisant la fonction displayPlayers du namespace game :

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

Pour voir en live le code, cliquez sur Pong

La suite du programme va consister à animer les raquettes, à 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 principale au rafraichissement de la page
  }
  
  var main = function() {
    // le code du jeu
    game.clearLayer(game.playersBallLayer);
    game.displayPlayers();
    game.moveBall();
    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 : "#FFD700",
    posX : 200,
    posY : 200,
    directionX : 1,
    directionY : 1,
    speed : 1,

    move : function() {
      this.posX += this.directionX * this.speed;
      this.posY += this.directionY * this.speed;
    },
	
    bounce : function() {
      if ( this.posX > game.groundWidth || this.posX < 0 )
        this.directionX = -this.directionX;
      if ( this.posY > game.groundHeight || this.posY < 0  )
        this.directionY = -this.directionY;			
			
    }
  },
   
  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);
  },

  moveBall : function() { 
    this.ball.move();
	this.ball.bounce();
    this.displayBall();
  }, 
  
  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);
  },

  clearLayer : function(targetLayer) {
	targetLayer.clear();
  }
  
};

game.display.js

game.display = {
  container : "",
  
  layer : {
    name : "",
    canvas : "",
    context2D : "",
    posX : null,
    posY : null,
    width : "",
    height : "",
    backgroundColor : "",
    zIndex : "",
	
    clear : function() {
      this.context2D.clearRect(0, 0, this.canvas.width, this.canvas.height);
    }
  },
  
  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.backgroundColor = 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é développer pong, pong html5, pong javascript, tutoriel html5, tutoriel jeu vidéo, tutoriel pong  |  15 commentaires

15 réponses à "Coder le jeu vidéo pong html5 – Animer la balle"

Répondre