Journal de bord: création d’un RTS en HTML5, jour 2

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

rtshtml5

Le github
Le projet GitHub : https://github.com/imikado/rtshtml5