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

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

Introduction
Il y a une semaine, en discutant avec des collègues d’un petit jeux que j’avait fait en html5, m’est venu l’idée un peu dingue de faire un jeu de stratégie temps réel.
L’idée était de faire un bon vieux RTS à l’ancienne, un peu comme Warcraft: notre madeleine de proust.
J’ai mis les sources sur Github en LGPLv3 afin de permettre d’apprendre de ce projet et de permettre à d’autre de se lancer pour s’amuser ou plus dans le developpement de jeux HTML5.

L’idée du journal
Au vu des soucis que j’ai rencontré en le développant, je me suis dit qu’écrire une sorte de journal de bord serait une bonne idée, que cela pourrait en intéresser plus d’un.
Je vais au fil du développement poster l’état d’avancement du projet ainsi que les problèmes rencontrés et les solutions trouvées.

Jour 1: Présentation du projet
Le projet est assez modeste pour qu’il soit réalisable et aille jusqu’au bout.
Une carte, avec la possibilité de scroller à l’aide de la souris, une miniature en bas à droite pour voir où nous sommes.
Vous débutez avec un personnage et un QG qui permet de créer des nouveaux « batisseurs »
Chaque bâtisseur peut créer l’équivalent d’une barrack warcraft pour créer des unités d’attaque.
Il y a un début de gestion de ressources: vous pouvez envoyer une unité « batisseur » à la mine d’or pour recueillir de l’or, ou aller chercher du bois.
Il y a une gestion de zone connu et inconnu qui evolue au fil de l’exploration.
Lorsque vous construisez un bâtiment, il ne peut être construit que dans la partie visible et libre, un gestion de couleur verte/rouge pour l’indiquer.

Quelques images
rtshtml5
Création d’un batiment
rtshtml52
rtshtml53

Présentation technique
Le projet se décompose en N fichiers:
rts.html : le fichier principal html incluant les fichiers javascripts, canvas…
rts.js: le fichier javascript principal gérant le chargement du jeu, + la boucle de mise à jour
rts_Game.js : la classe du jeu
rts_Build.js: les classes Build et Wood (pour les batiments, mines et arbres)
rts_Unit.js: la classe Unit (pour les unités)
lib3/canvas.js : la classe pour interagir plus facilement avec le canvas

Ce qu’il reste à faire
Gérer un temps de création d’unité et de bâtiment pour qu’ils ne soient plus immédiat.
Gestion d’attaque: une unité armé, doit, à proximité d’un enemie l’attaquer.
Gerer les points de vie + de défense.
Permettre de creer des batiments pour améliorer l’attaque et la défense.
Permettre la sélection multiple.
Création d’ennemie avec une petite intelligence artificielle pour permettre un mode mono joueur « tower defense ».
Créer un moteur de jeux réseau pour faire une partie multi-joueur.
Améliorer les graphismes ;)

Les infos supplémentaires
Le dépot github: https://github.com/imikado/rtshtml5