Journal de bord: création d’un RTS en HTML5, jour 2
Introduction
Ce billet fait suite au billet précédent: http://blog.developpez.com/ducodeetdulibre/p12404/developpement/journal-de-bord-creation-dun-rts-en-html5-jour-1
Dans ce billet je vais vous détailler les fichiers rts.js et canvas.js , leur utilisation, leur construction.
Faire une application HTML5/canvas
Mais avant, je dois vous expliquer le fonctionnement global d’une application HTML5: pour ceux qui ont déjà fait des jeux en flash, il faut se dire vous dire que l’HTML5 est plus austère: oubliez la scène, la timeline et les clips, ici juste un canvas, soit une image fixe.
Ce que l’on fait pour créer une animation, selectionner un élement.. ce sont des maths : aucun objet sur un canvas, juste des pixels de couleurs différentes imprimés ou éffacés sur une image (le canvas).
Pour simplifier un peu la gestion et les performances, on créé autant de canvas que de calques nécéssaires:
Un canvas pour le sol, un canvas pour les batiment, un autre pour les personnage, le brouillard, la selection…
lib3/canvas.js
Cette classe est très importante dans ce projet, elle permet de faciliter les interactions avec les canvas.
Vous pouvez ainsi coder plus facilement:
1 2 | var oCanvas = new Canvas('idDeMonCanvas'); oCanvas.drawRectStroke(x,y,largeur,hauteur,epaisseur,couleur); |
Au lieu de :
1 2 3 4 5 6 7 | var canvas = document.getElementById('idDeMonCanvas'); var ctx = canvas.getContext('2d'); ctx.beginPath(); ctx.lineWidth=epaisseur; ctx.strokeStyle=couleur; ctx.strokeRect(x,y,largeur,epaisseur); ctx.closePath(); |
Comme vous pouvez le voir c’est plus concis et plus agréable à écrire.
Voila à quoi ressemble cette classe:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | //CANVAS function Canvas(id){ this.canvas = document.getElementById(id); if(!this.canvas){ alert('cannot find "'+id+'"'); } this.ctx = this.canvas.getContext('2d'); this.width = this.canvas.width; this.height = this.canvas.height; this.fill_color = "#FFF"; this.stroke_color = "#000"; } Canvas.prototype={ isInside: function(pos) { return true; }, clear: function(){ this.ctx.clearRect(0, 0, this.width, this.height); }, clearRect: function(x,y,width,height){ this.ctx.clearRect(x, y, width, height); }, circle: function(p,r){ x = p.x*this.width; y = p.y*this.height; this.ctx.beginPath(); this.ctx.strokeStyle = this.stroke_color; this.ctx.moveTo(x+r,y); this.ctx.arc(x,y,r,0,TWO_PI,false); this.ctx.fill(); }, line: function(x1,x2){ this.ctx.beginPath(); this.ctx.strokeStyle = this.stroke_color; this.ctx.moveTo(x1.x*this.width,x1.y*this.height); this.ctx.lineTo(x2.x*this.width,x2.y*this.height); this.ctx.stroke(); }, drawRect : function(x,y,ilargeur,ihauteur,contour,fond){ this.ctx.beginPath(); this.ctx.lineWidth=1; this.ctx.strokeStyle=contour; this.ctx.fillStyle=fond; this.ctx.fillRect(x,y,ilargeur,ihauteur); this.ctx.strokeRect(x,y,ilargeur,ihauteur); this.ctx.closePath(); }, fillRect : function(x,y,ilargeur,ihauteur,fond){ this.ctx.beginPath(); this.ctx.lineWidth=0; this.ctx.fillStyle=fond; this.ctx.fillRect(x,y,ilargeur,ihauteur); this.ctx.closePath(); }, drawRectStroke : function(x,y,ilargeur,ihauteur,contour,width){ this.ctx.beginPath(); this.ctx.lineWidth=width; this.ctx.strokeStyle=contour; this.ctx.strokeRect(x,y,ilargeur,ihauteur); this.ctx.closePath(); }, fillText:function(x,y,texte,couleur){ this.ctx.textBaseline = 'top'; this.ctx.fillStyle=couleur; this.ctx.fillText(texte,x,y); }, drawLosange: function (x,y,ilargeur,ihauteur,couleur,fond){ this.ctx.lineWidth=1; if(couleur!='#000000'){ this.ctx.lineWidth=2; } this.ctx.beginPath(); this.ctx.moveTo(x,y+(ihauteur/2) ); this.ctx.lineTo(x+(ilargeur/2),y); this.ctx.lineTo(x+(ilargeur/1),y+(ihauteur/2)); this.ctx.lineTo(x+(ilargeur/2),y+(ihauteur/1)); this.ctx.lineTo(x,y+(ihauteur/2)); this.ctx.strokeStyle = couleur; this.ctx.stroke(); this.ctx.fillStyle=fond; this.ctx.fill(); this.ctx.closePath(); }, drawImage: function (img,x,y,width,height){ this.ctx.drawImage(img,x,y ,width,height ); }, isInThisLosange: function(x,y){ }, }; |
]
rts.js
Ce fichier contient plusieurs choses: les variables de contexte générales comme les dimensions d’une case, la fréquence de raffraichissement, les coordonnées du QG…
Mais aussi et surtout plusieurs fonctions clés : preload(), load() et run()
La fonction preload(), appellée au chargement de la page: elle instancie chaque Canvas/calques et la classe Map (pour charger les images)
Elle attend pour le moment une seconde avant de lancer le chargement de l’application, mais ceci devra être changé plus tard pour se basé sur le temps de chargement des images et non une durée arbitraire.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | function preload(){ oLayer_map=new Canvas('layer_map'); oLayer_apercu=new Canvas('layer_apercu'); oLayer_building=new Canvas('layer_building'); oLayer_perso=new Canvas('layer_perso'); oLayer_select=new Canvas('layer_select'); oLayer_buildingcreation=new Canvas('layer_newbuild'); oLayer_buildingcreation.ctx.globalAlpha=0.9; oLayer_brouillard=new Canvas('layer_brouillard'); oLayer_brouillard.ctx.globalAlpha=0.9; oLayer_cadre=new Canvas('layer_cadre'); oLayer_cadre.ctx.globalAlpha=0.2; oLayer_apercuCadre=new Canvas('layer_apercuCadre'); oLayer_apercuBuild=new Canvas('layer_apercuBuild'); oLayer_apercuBrouillard=new Canvas('layer_apercuBrouillard'); oLayer_apercuBrouillard.ctx.globalAlpha=0.9; //on instancie la classe Map et Game pour précharger un peu les images map = new Map(); oGame=new Game; setTimeout(load,1000); } |
La fonction load(), appellée 1 seconde apres le preload doit construire l’application:
Elle créé le batiment et l’unité de départ, puis appelle la construction de la map, celle de l’apercu et le cadre indiquant la zone affichée.
Elle permet également l’affichage des batiments, des zones utilisées pour le scrolling via la souris et les ressources disponibles (or/bois).
Et enfin appel la fonction run qui sera notre fonction de raffraichissement.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | function load(){ //on cache la div de chargemetn getById('loading').style.display='none'; //on construit la map, l'apercu //et le cadre d'information de la map affichée map.build(); map.buildApercu(); map.buildApercuCadre(); //on créé une unité de départ var oUnit =new Unit('soldat','WPface.png'); oUnit.x=4; oUnit.y=7; oUnit.build(); //on ajoute cette unité à un tableau tUnit //(pour pouvoir boucler dessus pour mettre à jour) oGame.tUnit.push(oUnit); //on créé le batiment de départ (QG) var oBuild=new Build('building','img3/build1.png'); oBuild.x=QGx; oBuild.y=QGy; oBuild.build(); //on ajoute ce batiment au tableau tBuild //(pour boucler et réafficher lors d'un scrolling) oGame.tBuild.push(oBuild); //on créé ici une mine d'or sur la map var oBuild=new Build('or','img3/mine-or.png'); oBuild.x=17; oBuild.y=17; oBuild.build(); //on ajoute cette mine d'or au tableau tBuild oGame.tBuild.push(oBuild); //on affiche les batiments sur la carte oGame.rebuild(); //on affiche les ressouces de départ (or/bois) oGame.buildRessource(); //on affiche les zones réactives //pour scroller la map avec la souris oGame.drawDirection(); //on commencera la boucle de raffraichissement run() //dans N secondes setTimeout(run,fps); } |
La fonction run(), appelée toutes les N secondes (variable fps)
Cette fonction est appelée regulièrement, elle permet de créer cet effet d’animation: le déplacement d’une unité, la création d’un batiment, le scrolling…
Elle vérifie si on doit scroller et dans ce cas là appel la reconstruction du calque/canvas contenant les batiments
Sinon en permanence boucle sur l’ensemble des unités pour faire évoluer leurs déplacements.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | function run(){ //si la souris est sur une zone active de scroll if(sDirection=='up'){ //scroll haut oGame.goUp(); }else if(sDirection=='down'){ //scroll bas oGame.goDown(); }else if(sDirection=='left'){ //scroll gauche oGame.goLeft(); }else if(sDirection=='right'){ //scroll droite oGame.goRight(); }else{ //sinon on affiche les zones réactives oGame.drawDirection(); } //on raffraichit les unités oGame.refreshUnit(); //dans N secondes on appelera de nouveau cette fonction setTimeout(run,fps); } |
Quelques illustrations pour rappel
Le github
Le projet GitHub : https://github.com/imikado/rtshtml5