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 :
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 :
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 :
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 :
La prochaine étape de cette série va consister à faire animer la balle en la faisant rebondir sur les murs à suivre ici.
il manque le code complet de cette étape 🙁 …
Voilà qui est corrigé
ah une question au fait,
Y a-t-il une raison spéciale de déclarer les fonctions de cette manière:
var foo = fonction (){}
plutôt que
fonction foo (){} ?
Avec function foo() {}, tu crées une fonction nommée, la fonction est définie avant l’exécution du code, lorsque javascript parse le code.
Avec var foo = function() {}, tu crées une variable qui pointe sur une fonction anonyme, elle est définie à l’exécution. Tu peux donc la modifier ou rédéfinir à la volée.
Quelque soit la solution choisie, les résultats sont similaires. Le choix se fait selon les besoins, et/ou les habitudes.
Super tuto !
Je m’amuse bien a manipuler toutes ces techniques que j’ai découverte grâce à ce blog.
j’attends la suite avec impatience,
bravo !
++
Salut,
Une petite remarque (c’est pas obligatoire), pour que ton jeu soit insérable dans une page existante et qu’il soit non intrusif, il faudrait changer le window.onload par un window.addEventListener(“load”,initialisation,false); ou document.addEventListener(“DOMContentLoaded”,initialisation,false);
Kran
J’aime bien, t’es attentif comme lecteur.
Permet moi de te contredire : le addEventListener n’est pas la norme, il résulte d’un ajout d’un navigateur (je ne sais plus lequel) qui date.
Le “onEvent” est la norme, même si addEventListener apporte une certaine finesse dont on a pas besoin ici et que la gestion des événements est expliquée avec cette fonction dans tous les tutos.
Plutôt que addEventListener, mieux vaut envisager d’utiliser la gestion d’événement de JQuery qui est très fine.
Après chacun fait comme il l’entend.
Enfin le code tel qu’il est n’est pas intrusif :
(function () {})(); qui encapsule le code l’en empêche.
Après rien ne t’empêche de proposer un article sur le sujet.
Salut,
merci pour ton compliment.
Je suis entièrement d’accord avec toi sur le fait que addEventListener n’est pas la norme.
Cependant, je reviens sur le window.onload = initialisation;, je me suis peut-être mal exprimé sur le mot intrusif.
Par exemple, je veux utiliser une lib tiers qui contient un window.onload et ensuite je veux intégrer ton jeux dans la même page.
Le résultat sera le suivant ton onload va écraser le onload de la lib tiers.
par exemple :
(function () {
window.onload=function(){alert(‘ok’);}
})();
(function () {
// debut du code isole
// le code de ton jeu
window.onload = initialisation; // appel de la fonction initialisation au chargement de la page
})();
Attention, je ne dis que j’ai la science infuse, je veux juste mettre l’accent sur une potentielle source d’erreur chez les débutants, je ne me permettrai pas de dire que cette solution est la meilleur