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

Introduction
Ce billet fait suite au billet: http://blog.developpez.com/ducodeetdulibre/p12416/developpement/journal-de-bord-creation-dun-rts-en-html5-jour-7

Ajourd’hui nous allons passer aux sprites
L’idée est d’éviter d’avoir une image par éléments, mais 2-3 images qui contiendrait l’ensemble afin d’avoir peu de fichiers à télécharger
On va créer 2 images: une qui contiendra les images 1×1 (map, unités, arbres…) cases et l’autre les images 2×2 (batiment, mine d’or…)

gimp-sprites2x2
rts_sprite

Ensuite on va créer une classe qui permettra dans un premier temps de charger les 2 images, et dans un second temps d’identifier chaque élément du sprite

Création de la classe de gestion de sprites

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
function Images(){
    this.tOImg=new Array();
    this.tDetail=new Array();
    this.counter=0;
}
Images.prototype={
    //methode qui permet de charger une image
    load:function(src,idImg){
        this.tOImg[idImg]=new Image();
        this.tOImg[idImg].src=src;
        this.tOImg[idImg].onload=function(){
            oImages.counter++;
            preload2();
        }
    },
    //methode qui permet d'identifier un élément du sprite
    setDetailOnId:function(id,y,x,width,height,idImg){
        this.tDetail[id]=new Array();
        this.tDetail[id]['x']=x;
        this.tDetail[id]['y']=y;
        this.tDetail[id]['width']=width;
        this.tDetail[id]['height']=height;
        this.tDetail[id]['idImg']=idImg;
    },
    //methode qui permet de dessiner un element sur un des canvas
    drawImageOnLayer:function(id,x,y,width,height,sLayer){
        var oCanvasTmp;
        if(sLayer=='map'){
            oCanvasTmp=oLayer_map;
        }else if(sLayer=='apercu'){
            oCanvasTmp=oLayer_apercu;
        }else if(sLayer=='perso'){
            oCanvasTmp=oLayer_perso;
        }else if(sLayer=='building'){
            oCanvasTmp=oLayer_building;
        }
       
        oCanvasTmp.drawImage2(this.tOImg[ this.tDetail[id]['idImg'] ],this.tDetail[id]['x'],this.tDetail[id]['y'],this.tDetail[id]['width'],this.tDetail[id]['height'],x,y,width,height);
       
    },
};

Chargement des sprites et identification
Il faut ensuite identifier chaque éléments:
On créé un tableau qui représente virtuellement les éléments sur le sprite
En bouclant dessus, on identifie dans la classe Images ceux-ci

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
oImages=new Images();
   
var tDetailTmp=new Array();
tDetailTmp=[
    ['case-beige2','case-water','case-beige','case-wood'],
    ['unit-worker'],
    ['unit-soldier'],
    ['unit-archer'],
];
for(var y=0;y<tDetailTmp.length;y++){
    for(var x=0;x<tDetailTmp[y].length;x++){
        oImages.setDetailOnId(tDetailTmp[y][x],y*40,x*40,40,40,'1x1');
    }
}
var tDetailTmp=new Array();
tDetailTmp=[
    ['build-SoldierHouse','build-SoldierHouse_-2','build-SoldierHouse_-1'],
    ['build-QG','build-QG_-2','build-QG_-1'],
    ['build-ArcherHouse','build-ArcherHouse_-2','build-ArcherHouse_-1'],
   
    ['build-mineOr'],
];
for(var y=0;y<tDetailTmp.length;y++){
    for(var x=0;x<tDetailTmp[y].length;x++){
        oImages.setDetailOnId(tDetailTmp[y][x],y*80,x*80,80,80,'2x2');
    }
}

oImages.load('img3/sprite1x1.png','1x1');
oImages.load('img3/sprite2x2.png','2x2');

Dessin d’une image issu du sprite
Pour dessiner, il suffit d’appeler l’objet ainsi.
Exemple pour la classe Build: on dessine l’id « this.idImg sur le canvas « building »

1
oImages.drawImageOnLayer(this.idImg+this.sSprite,(this.x-currentX)*widthCase,(this.y-currentY)*heightCase,widthCase*2,widthCase*2,'building');

Animation de construction d’un batiment
Pour feter l’arrivé des sprites j’ai ajouté un cycle de construction pour les batiments:
Dans la classe Game, ajout de la méthode:

1
2
3
4
5
6
7
8
9
refreshBuild:function(){
    for(var i=0;i< this.tBuild.length;i++){
        var oBuild= this.tBuild[i];
        if(oBuild.level  3){
    oGame.refreshBuild();
    iRefreshBuild=0;
}

iRefreshBuild++;

note: je gère un compteur pour que le batiment ne se construise pas trop vite.

Optimisation du code
En passant au sprite, il a y a eu une autre optimisation de faite: comme on ne charge que 2 images en début de script, avec un mécanimes d’attente de chargement, on n’a plus besoin de se demander/ de coder de méthode onload asynchrone
Avant:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
build:function(){
    if(this.oImage==''){
        this.oImage=new Image(this);
        this.oImage.src=this.src;
        this.oImage._x=this.x;
        this.oImage._y=this.y;
        this.oImage.onload=this.drawImage;
    }else{
        oLayer_building.drawImage(this.oImage ,(this.x-currentX)*widthCase,(this.y-currentY)*heightCase,widthCase*2,widthCase*2);
    }
    (...)
},
drawImage:function(){
    oLayer_building.drawImage(this ,(this._x-currentX)*widthCase,(this._y-currentY)*heightCase,widthCase*2,widthCase*2);
},

Après:

1
2
build:function(){
    oImages.drawImageOnLayer(this.idImg+this.sSprite,(this.x-currentX)*widthCase,(this.y-currentY)*heightCase,widthCase*2,widthCase*2,'building');

Plus besoin de gérer deux modes: si l’image n’existe pas , avec gestion asynchrone via onload + si l’image existe
On affiche simplement l’image via l’objet oImages (via les sprites)
Le github
Le projet GitHub : https://github.com/imikado/rtshtml5

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

Introduction
Ce billet fait suite au billet: http://blog.developpez.com/ducodeetdulibre/p12415/developpement/journal-de-bord-creation-dun-rts-en-html5-jour-6

Modification de la selection d’unités
Si on veut par la suite pouvoir faire une multiple selection, on va remplacer la variable selected par un tableau tSelected
Et ainsi en selectionnant une unité on l’ajoute à ce tableau

1
2
3
4
5
6
7
8
9
10
select:function(oMix){
    //on enregistre l'unité/batiment
    this.tSelected.push(oMix);
   
    //on demande son dessin
    this.drawSelected();
   
    //on affiche la navigation
    oMix.buildNav();
},

Et lors de l’affichage des unités sélectionnés, on va boucler sur celui-ci

1
2
3
4
5
6
7
8
9
drawSelected:function(){
    //on efface le calque
    oLayer_select.clear();
   
    for(var i=0;i<this.tSelected.length;i++){
        //on dessine un cadre sur un des calques au dimension de l'élement
        oLayer_select.drawRectStroke((this.tSelected[i].x-currentX)*widthCase,(this.tSelected[i].y-currentY)*heightCase,this.tSelected[i].width,this.tSelected[i].height,'#880044',3);
    }
},

Permettre de sélectionner plusieurs unités avec shift
Premièrement ajouter un onkeydown appelant oGame.keydown(event)
Puis nous définissons cette méthode pour qu’elle enregistre une variable shiftClicked

1
2
3
4
5
6
7
8
keydown:function(e){
    var touche = e.keyCode;

    //si shirt, on enregistre que shift est pressé
    if(touche==16){
        this.shiftClicked=1;
    }
},

Et l’on prévoit le fait de relacher la touche shift pour ne plus sélectionner plusieurs unités

1
2
3
keyup:function(e){
    this.shiftClicked=0;
},

Enfin on modifie la methode click de la classe Game pour prendre en compte le fait que la touche est pressée

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//si il y a une unité
if(oUnit){
    //si la touche shift n'est pas cliqué en efface le tableau de sélection
    if(!this.shiftClicked){
        this.clearSelect();
    }
    //on selectionne celle-ci
    this.select(oUnit);
}else if(oBuild){
    this.clearSelect();
    //si  il y a un batiment,
    //on le selectionne
    this.select(oBuild);
}else{
    //sinon on supprime la selection sur le canvas
    console.log('pas trouve');
    this.clearSelect();
}

Correction des déplacements des unités
Si on sélectionne plusieurs unités et que l’on clique à un endroit, celles-ci vont fusionner.
Pour éviter cela, on modifie la méthode checkCoord (de la classe Game) pour prendre en compte également les coordonnées des unités et pas seulement des batiments

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
checkCoord:function(x,y){
    y=parseInt(y+0);
    x=parseInt(x+0);
    if(this.tCoordBuild[ y ] && this.tCoordBuild[ y ][ x ] && this.tCoordBuild[ y ][ x ]!=''){
        console.log('not libre tCoordBuild[ '+y+' ][ '+x+' ]');
        return false;
    }
   
    if(this.tCoordUnit[ y ] && this.tCoordUnit[ y ][ x ] && this.tCoordUnit[ y ][ x ]!=''){
        console.log('not libre tCoordUnit[ '+y+' ][ '+x+' ]');
        return false;
    }
   
    if(map.tMap[y] && map.tMap[y][x] && map.tMap[y][x]==3){
   
        return true;
       
    }
    return false;

},

Tant que l’on corrige, on va également indiquer que l’on ne peut entrer dans un QG que dans le cas d’une « ronde »
Dans la méthode refreshUnit de la classe Game, on va verifier que les coordonnées de cycle sont renseigné
On passe de

1
2
//si la cible est le QG
if(aBuild && aBuild.name=='QG' ){

à

1
2
//si la cible est le QG et que l'on est en "ronde"
if(aBuild && aBuild.name=='QG' && oUnit.cycleToX!=''){

Idem pour le bois

Gérons les arbres
Un arbre doit disparaitre au bout d’un certains nombre d’itération: ce n’est pas une ressource illimitée
Pour cela, on va ajouté une propriété que l’on va décrémenter sur la classe Wood afin de le faire disparaitre une fois épuisé
Dans la classe wood

1
2
3
4
5
6
7
8
9
10
11
12
13
function Wood(){
    this.name='wood';
    this.shortname='Bois';
    this.src="img3/case-wood.png";
   
    this.x=0;
    this.y=0;
   
    this.width=20;
    this.height=20;
   
    this.ressource=30;
}

Ensuite on va indiquer que supprimer l’arbre apres avoir épuisé l’arbre (dans la méthode refreshUnit de la classe Game)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
}else if(aBuild && aBuild.name=='wood' && oUnit.counter >= 8 && oUnit.cycleToX!=''){
    //on indique à l'unité qu'elle transporte 10
    oUnit.wood=10;
   
    //a chaque iteration on decremente la ressource
    aBuild.ressource-=10;
    //si l'arbre est épuisé, on le supprime de la carte
    if(aBuild.ressource<=0){
        aBuild.clear();
    }
   
    oUnit.x=newX;
    oUnit.y=newY;
   
    //on remet le compteur à 0
    oUnit.counter=0;
   
    //on redéfinit la nouvelle cible (c'est un cycle)
    oUnit.setTarget(oUnit.cycleFromX,oUnit.cycleFromY);

Et l'on va indiquer à l'arbre qu'avec sa methode clear il doit se supprimer de la map

1
2
3
4
5
6
7
clear:function(){
    map.tMap[this.y][this.x]=3;//un arbre c'est 4, 3 c'est une case vide
    oGame.clearBuild(this);
    sDirection='refresh';

    console.log('on supprime l arbre y:'+this.y+' x:'+this.x);
}

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

Introduction
Ce billet fait suite au billet: http://blog.developpez.com/ducodeetdulibre/p12413/developpement/journal-de-bord-creation-dun-rts-en-html5-jour-5

Nous avons dans les précédents billets expliqué chaque fichier du projet, nous allons à partir d’aujourd’hui continuer ce rts pour arriver à un jeu fini ;)

Gerer les couts des batiments
Plutot que d’avoir écrit en dur 100 d’or pour les batiments, on va stoquer dans l’objet batiment le cout en or et en bois de chaque batiment

Pour cela on va editer la classe Build, on y ajoute 2 propriétés costOr et costWood
J’en ai profité pour remanier un peu le constructeur: cette fois on ne recoit que le type de batiment et le constructeur fait le reste (adresse de l’image, cout…)

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
//build
function Build(name,src){
    this.name=name;
    this.oImage='';
   
    this.x=0;
    this.y=0;
    this.life=100;
   
    this.width=widthCase*2;
    this.height=widthCase*2;
   
    this.color='#064474';
    this.bVisibility=1;
   
    this.costOr=0;
    this.costWood=0;
   
    if(this.name=='or'){
        this.color='#e8bb08';
        this.bVisibility=0;
        this.shortname='Mine d\'or';
        this.src='img3/mine-or.png';
        this.unitCreation ='';
    }else if(this.name=='QG'){
        this.shortname='Quartier général';
        this.src='img3/build1.png';
       
        this.unitCreation =new Unit('Worker');
    }else if(this.name=='SoldierHouse'){
        this.shortname='Batiment des soldats';
        this.src='img3/build2.png';
       
        this.costOr=100;
        this.costWood=100;
       
        this.unitCreation =new Unit('Soldier');
    }else if(this.name=='ArcherHouse'){
        this.shortname='Batiment des archers';
        this.src='img3/build3.png';
       
        this.costOr=200;
        this.costWood=50;
       
        this.unitCreation =new Unit('Archer');
    }
   
}

J’ai également remanié la classe unit, c’est également le constructeur qui fait tout: image, nom et batiments constructibles.

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
function Unit(name){
    this.name=name;
    this.oImage='';
   
    this.x=0;
    this.y=0;
    this.life=100;
   
    this.targetX='';
    this.targetY='';
   
    this.width=widthCase;
    this.height=widthCase;
   
    this.oBuildOn=null;
   
    this.counter=0;
    this.or=0;
    this.wood=0;
   
    this.cycleFromX='';
    this.cycleFromY='';
   
    this.cycleToX='';
    this.cycleToY='';
   
    this.tBuildCreation=new Array();
   
    if(this.name=='Soldier'){
        this.shortname='Soldat';
        this.src='img3/WPface.png';
       
    }else if(this.name=='Archer'){
        this.shortname='Archer';
        this.src='img3/WC.png';
           
    }else if(this.name='Worker'){
        this.shortname='Ouvrier';
        this.src='img3/WK.png';
       
        this.tBuildCreation.push(new Build('SoldierHouse'));
        this.tBuildCreation.push(new Build('ArcherHouse'));
    }
}

Limitation du scroll à la carte
Pour éviter que l’on puisse scroller à l’infini, on va ajouter un garde fou à la classe Game

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
Game.prototype={
    drawDirection:function(){
        oLayer_cadre.clear();
        //left
        oLayer_cadre.drawRect(0,0,10,oLayer_map.height,'#eeaf17','#eeaf17');
        //right
        oLayer_cadre.drawRect(oLayer_map.width-10,0,10,oLayer_map.height,'#eeaf17','#eeaf17');
        //bottom
        oLayer_cadre.drawRect(0,oLayer_map.height-10,100,10,'#eeaf17','#eeaf17');
        oLayer_cadre.drawRect(500,oLayer_map.height-10,300,10,'#eeaf17','#eeaf17');
        //top
        oLayer_cadre.drawRect(0,0,oLayer_map.width,10,'#eeaf17','#eeaf17');
    },
    goLeft:function(){
        if(currentX-1  map.tMap[0].length){
            return ;
        }
       
        currentX+=1;
        this.rebuild();
        oLayer_cadre.drawRect(oLayer_map.width-10,0,10,oLayer_map.height,'#eeaf17','#eeaf17');
    },
    goDown:function(){
        if(currentY+1+maxY > map.tMap.length){
            return ;
        }
       
        currentY+=1;
        this.rebuild();
        oLayer_cadre.drawRect(0,oLayer_map.height-10,100,10,'#eeaf17','#eeaf17');
        oLayer_cadre.drawRect(500,oLayer_map.height-10,300,10,'#eeaf17','#eeaf17');
    },
    goUp:function(){
        if(currentY-1 < 0){
            return ;
        }
       
        currentY-=1;
        this.rebuild();
        oLayer_cadre.drawRect(0,0,oLayer_map.width,10,'#eeaf17','#eeaf17');
    },

Permettre de scroller via la mini map
Sur une carte très grande, on imagine pas devoir scroller à la souris, ce serait trop long
On préfèrerai cliquer directement sur la map
Pour cela, on va dans un premier temps modifier la partie html afin de prendre en compte l’evenement clic gauche et droite sur la carte d’apercu

Puis dans la classe Game, on rajouter une methode clickApercu

1
2
3
4
5
6
7
8
9
10
11
12
13
clickApercu:function(e){
    //recuperation des coordonnées "tableau" x y
    var x=this.getXApercu(e);
    var y=this.getYApercu(e);
   
    currentX=x-(maxX/2);
    currentY=y-(maxY/2);
   
    if(currentX < 0 ){ currentX=0;}
    if(currentY < 0 ){ currentY=0;}
   
    sDirection='refresh';
},

J'ai également ajouté dans la fonction un rafraichissement des batiments dans le cas d'un scroll via l'apercu

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
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{
        //si direction refresh, on redessine la map
        if(sDirection=='refresh'){
            oGame.rebuild();
            sDirection='';
        }
       
        //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);
}

Permettre de déplacer une unité en cliquant directement sur l’aperçu
Afin de rendre la tache plus simple, on va factoriser le contenu de la méthode clickdroit (de la classe Game)
En créant une méthode goto, il est plus simple ainsi de permettre via un clic droit sur l’aperçu de déplacer l’unité

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
goto:function(x,y){
    //recuperation de batiment sur ces coordonnées
    var aBuild=this.getBuild(x,y);

    //si une unité est sélectionnée et que le batiment cliqué est une mine ou du bois
    if(this.selected!='' && aBuild && ( aBuild.name=='or' || aBuild.name=='wood' )){
        //on créé une ronde du bois/or vers le QG
        //pour alimenter les ressources Or/bois
       
        //on indique que la destination de cycle c'est la mine d'or ou l'arbre
        this.selected.cycleToX=x;
        this.selected.cycleToY=y;

        //on indique que la provenance du cycle c'est le QG
        this.selected.cycleFromX=QGx;
        this.selected.cycleFromY=QGy;

        //on donne comme cible de deplacement la mine d'or/l'arbre cliqué
        this.selected.setTarget(x,y);
    }else if(!this.isWalkable(x,y)){
        //si la case n'est pas accessible, il ne se passe rien
   
    }else if(this.selected!=''){
        //si la case est accessible, on y indique à l'unité d'y aller
        this.selected.setTarget(x,y);
    }
},
//appelé lors d'un clic droit sur le canvas (pour un deplacement d'unité)
clickdroit:function(e){
    //recuperation des coordonnées "tableau" x y
    var x=this.getX(e);
    var y=this.getY(e);

    this.goto(x,y);
    return false;

},
clickdroitApercu:function(e){
    //recuperation des coordonnées "tableau" x y
    var x=this.getXApercu(e);
    var y=this.getYApercu(e);
   
    this.goto(x,y);
    return false;
},

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

Introduction
Ce billet fait suite au billet: http://blog.developpez.com/ducodeetdulibre/p12411/developpement/journal-de-bord-creation-dun-rts-en-html5-jour-4

Dans ce billet, nous verrons la classe Unit, qui permet de gerer des unités

Le constructeur d’unité

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
function Unit(name,src){
    //le nom type d'unité
    this.name=name;
    //l'adresse de l'image
    this.src='img3/'+src;
    this.oImage='';
   
    //les coordonnées + les vies
    this.x=0;
    this.y=0;
    this.life=100;
   
    //la destination cible
    this.targetX='';
    this.targetY='';
   
    //la largeur/hauteur de l'unité
    this.width=widthCase;
    this.height=widthCase;
   
    //contiendra le moment venu un batiment à construire
    this.oBuildOn=null;
   
    //compteur utilisé lors de la récupération de ressource
    this.counter=0;
    //ressources or/bois transporté
    this.or=0;
    this.wood=0;
   
    //pour une ronde point de départ
    this.cycleFromX='';
    this.cycleFromY='';
   
    //pour une ronde point d'arrivée
    this.cycleToX='';
    this.cycleToY='';
   
    //tableau contenant les batiments que l'unité peut construire
    this.tBuildCreation=Array();
   
    if(this.name=='soldat'){
        this.tBuildCreation[0]=new Buildcreation('buid2','build2.png');
    }
}

L’affichage de l’unité
Méthode utilisée pour afficher l’unité
Comme vous pouvez le voir il y a également une partie pour la construction effective d’un batiment.

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
build:function(){
    //partie affichage de l'image de l'unité sur le canvas
    if(this.oImage==''){
        this.oImage=new Image(this);
        this.oImage.src=this.src;
        this.oImage._x=this.x;
        this.oImage._y=this.y;
        this.oImage.onload=this.drawImage;
    }else{
        oLayer_perso.drawImage(this.oImage ,((this.x-currentX)*widthCase),((this.y-currentY)*heightCase),widthCase-2,widthCase-2);
       
        oLayer_perso.fillRect((this.x-currentX)*widthCase,((this.y-currentY)*heightCase)+heightCase-2,widthCase,2,'#00ff00');
       
    }
   
    //si l'unité doit construire un batiment, et qu'elle se trouve sur les lieux de la construction
    if(this.oBuildOn && this.x+1==this.oBuildOn.x && this.y==this.oBuildOn.y){
       
        //création du batiment à l'emplacement
        var aBuild=new Build(this.oBuildOn.name,this.oBuildOn.src);
        aBuild.x=this.oBuildOn.x;
        aBuild.y=this.oBuildOn.y;
        aBuild.build();
       
        //ajout du batiment à la liste des batiments (pour la reconstruction lors des scroll)
        oGame.tBuild.push(aBuild);
        //on sauvegarde les coordonnées du batiments
        oGame.saveBuild(aBuild);
       
        //on reset les propriétés de construction
        oGame.buildcreation='';
        this.buildOnX='';
        this.buildOnY='';
        this.oBuildOn='';
       
        //on décrément la ressource or
        oGame.iOr-=100;
        //on réactualise les ressources
        oGame.buildRessource();
        //on reset la sélection
        oGame.clearSelect();
       
    }
    //on enregistre les nouvelles coordonnées de l'unité
    oGame.saveUnit(this);
},

Quelques remarques, vous pouvez lire certaines portions de code qui seront amenés à être modifié: par exemple le fait que l’on décrémente uniquement l’or de 100 unités et pas le bois lorsque l’on créé un batiment.
Cette notion de cout de construction sera ensuite stoquée dans la classe build afin de permettre d’avoir des batiments de prix différents (or/bois)

Création de la navigation
Lorsque l’on sélectionne une unité, on ne va pas avoir les mêmes possibilités en fonction du type d’unité.
C’est ici qu’intervient la méthode buildNav qui va construire la navigation correspondant à l’unité.

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
buildNav:function(){
    var sHtml='';
   
    if(this.name=='soldat'){
        sHtml='<p><img src="'+this.src+'"></p>';
       
        sHtml+='<h2>Construction</h2>';
       
        var sEnabled='';
       
        //on boucle sur les batiments que l'unité peu construire
        for(var i=0;i&lt;this.tBuildCreation.length;i++){
            if(oGame.iOr &lt; 100){
                 sHtml+=&#039;';
            }else{
                sHtml+='
';
            }
           
            sHtml+='
';
        }
       
    }else{
        sHtml='
<p><img src="'+this.src+'"></p>';
       
    }
   
    getById('
nav').innerHTML=sHtml;
},

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

Introduction
Ce billet fait suite au billet: http://blog.developpez.com/ducodeetdulibre/p12406/developpement/journal-de-bord-creation-dun-rts-en-html5-jour-3

Dans ce billet je vais vous détailler le fichier rts_Map.js, son utilisation, sa construction.

Faire une application HTML5/canvas: construire la Map
Dans ce jeux de stratégie, comme dans beaucoup d’autres jeux, la carte est trop grande pour être affichée dans son ensemble,.
Il faut donc afficher une partie de la carte, et permettre de visualiser ce que l’on regarde par rapport à la carte complète.

C’est là qu’intervient la classe Map (fichier rts_Map.js)
Comme pour la classe Game, nous allons ici voir bloc par bloc

Création de la map

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
build:function(){
         
        for(var y=0;y&lt; maxY;y++){
            for(var x=0;x&lt; maxX;x++){
               
                //decalage x/y en fonction du scrolling
                var x2=x+currentX;
                var y2=y+currentY;
                 
                if(this.tMap[y2] &amp;&amp; this.tMap[y2][x2]){
                    //si c&#039;est un arbre
                    if(this.tMap[y2][x2]==4){
                        //on dessine un case normale
                        this.drawImage( 3 ,x,y);
                        //puis on créé un objet arbre par dessus
                        var oWood=new Wood();
                        oWood.x=x;
                        oWood.y=y;
                        oWood.build();
                       
                        //on ajoute cette arbre au tableau des batiments
                        //pour les reconstruire lors du scrolling
                        oGame.tBuild.push(oWood);
                    }
                    //on dessine sur le canvas la valeur du tableau
                    this.drawImage( this.tMap[y2][x2] ,x,y);
                   
                }
            }  
        }
       
    },
    //la methode pour dessiner sur le canvas
    drawImage:function(iImg,x,y){
        if(!this.tOImg[iImg]){
            var oImg=new Image();
            oImg.src=&#039;img3/&#039;+this.tImg[iImg];
            oImg._x=x;
            oImg._y=y;
            oImg.onload=function(){
                oLayer_map.drawImage(this,this._x*widthCase,this._y*heightCase,widthCase,widthCase);
               
            }
            this.tOImg[iImg]=oImg;
           
        }else{
            oLayer_map.drawImage(this.tOImg[iImg],x*widthCase,y*heightCase,widthCase,widthCase);
        }
       
    },

Petite parenthèse sur les images
Pensez bien que lorsque vous souhaitez dessiner une image sur un canvas, vous n’etes pas en local: les images de votre jeu ne sont pas chargées.
Pour gerer ce mode asynchrone: on créé un objet image, on lui assigne des propriétés comme ces coordonnées et enfin on lui indique du code à éxécuter au moment du chargement avec « onload »
Mais il faut également prévoir le fait que l’image ai déjà été chargée précédement.
C’est pour cela que je stoque ici l’objet image dans un tableau de propriété de la classe, ainsi, si l’objet existe on le dessine tout de suite.
En revanche si il n’existe pas on instancie avec les propriétés ainsi que la méthode onload.

Méthode de reconstruction
A chaque scroll, il faut redessiner la map: on se déplace sur la carte, mais il ne faut pas instancier de nouveaux objets arbres
C’est pour cela qu’il y a une méthode rebuild

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
rebuild:function(){
    for(var y=0;y&lt; maxY;y++){
        for(var x=0;x&lt; maxX;x++){
           
            var x2=x+currentX;
            var y2=y+currentY;
             
            if(this.tMap[y2] &amp;&amp; this.tMap[y2][x2]){
                if(this.tMap[y2][x2]==4){
                    this.drawImage( 3 ,x,y);
                }
                this.drawImage( this.tMap[y2][x2] ,x,y);
               
            }
        }  
    }
},

La construction de l’aperçu
Il faut également afficher l’aperçu de la carte.
La methode buildApercu permet cela, cette méthode ressemble beaucoup à la méthode build
Les différences sont: on dessine les cases beaucoup plus petites, on dessine sur un calque supérieur un cadre indiquant ce que l’on voit en détail.

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
//construction de l'apercu
    buildApercu:function(){
        var maxMiniY=this.tMap.length;
        var maxMiniX=this.tMap[0].length;
       
        for(var y=0;y&lt; maxMiniY;y++){
            for(var x=0;x&lt; maxMiniX;x++){
               
                var x2=x;
                var y2=y;
               
                if(this.tMap[y2] &amp;&amp; this.tMap[y2][x2]){
                    if(this.tMap[y2][x2]==4){
                        this.drawMiniImage( 3 ,x,y);
                    }
                    this.drawMiniImage( this.tMap[y2][x2] ,x,y);
                }
            }
        }
        oLayer_apercuBrouillard.fillRect(0,0,400,400,&#039;#000000&#039;);
    },
    drawMiniImage:function(iImg,x,y){
        oLayer_apercu.drawImage(this.tOImg[iImg],x*this.miniWidth,y*this.miniWidth,this.miniWidth,this.miniWidth); 
    },
    //dessin du cadre indiquant la partie affichée en détail
    buildApercuCadre:function(){
        oLayer_apercuCadre.clear();
        oLayer_apercuCadre.drawRectStroke(currentX*this.miniWidth,currentY*this.miniWidth,this.miniWidth*maxX,this.miniWidth*maxY,&#039;#ff0000&#039;,2);
    },

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

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

Introduction
Ce billet fait suite au billet précédent: http://blog.developpez.com/ducodeetdulibre/p12405/developpement/journal-de-bord-creation-dun-rts-en-html5-jour-2

Dans ce billet je vais vous détailler le fichier rts_Game.js, son utilisation, sa construction.

Faire une application HTML5/canvas : les interactions
J’expliquai lors du dernier billet que l’utilisation du canvas était un peu moins confortable que de faire du flash.
Une chose toute bête c’est d’identifier où l’on clique: sur une unité, sur un batiment, un arbre ou dans le vide.
Pour cela plusieurs choses: on recupère l’evenement clic, ses coordonnées, on leur soustrait le scroll du navigateur.
Puis on cherche à savoir si on trouve dans un tableau indexé par coordonnées x y un élément.

Cette méthode se trouve dans la classe Game.

La classe Game, fichier rts_Game.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//Game
function Game(){
    //tableau de coordonnées des unités
    this.tCoordUnit=Array();
    //tableau de corrdonnées des batiments/arbres/mine d'or
    this.tCoordBuild=Array();
    //le mode en cours
    this.mode='';
    //l'element selectionné (unité/batiment...)
    this.selected='';
    //le batiment selectionné à construire
    this.buildcreation='';
    //le tableau des cases visibles sur la carte
    this.tVisible=Array();
    //tableau contenant tous les batiments (utilisé pour reconstruire la map lors d'un scroll)
    this.tBuild=Array();
    //idem pour les unités
    this.tUnit=Array();
   
    //ressources
    this.iOr=250;
    this.iWood=150;
}

Je ne vais pas ici coller la classe Game en entier (plus de 400 lignes), je vais plutot présenter bloc par bloc pour expliquer certains principe de développement
Pour rappel, l’ensemble du projet est disponible sur github (voir en bas d’article)

La récupération des coordonnées
Lorsque l’on clique sur un endroit du canvas, il faut faire plusieurs choses: récupérer l’evenement, recupérer les coordonnées du clic, puis diviser par la taille des cases pour récupérer les coordonnées du tableau.

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
//recuperation de X + le decalage de la scrollbar
getXmouse:function(e){
    if(e &amp;&amp; e.x!=null &amp;&amp; e.y!=null){
        return e.x +document.body.scrollLeft;
    }else{
        return e.clientX +document.body.scrollLeft;
    }
},
//recuperation de X + le decalage de la scrollbar
getYmouse:function(e){
    if(e &amp;&amp; e.x!=null &amp;&amp; e.y!=null){
        return e.y + document.body.scrollTop;
    }else{
        return e.clientY + document.body.scrollTop;
    }
},
//recuperation de la coordonnée de tableau: divisition de x par la largeur d'une case
getX:function(e){
    var x=this.getXmouse(e);
    x=parseInt( x/widthCase);

    //ajout du decallage (scrolling sur la map)
    return x+currentX;
},
getY:function(e){
    var y=this.getYmouse(e);
    y=parseInt( y/widthCase);

    return y+currentY;
},

Le clic gauche
Lorsque l’on veut séléctionner une unité, on doit cliquer sur l’image de celle-ci.
Mais malheureusment, cette image est un dessin sur le canvas, il n’y donc pas possibilité de mettre un evement sur l’image.
On doit récupérer les coordonnées du clic pour retrouver l’image en dessous.
Pour cela, on recupere les coordonnées « tableau » puis l’on demande si il y a une unité ou un batiment en dessous.

Lors du clic gauche, si on a demandé à l’unité de construire un batiment, on va vérifier que l’on peut le construire sur ces coordonnées

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
    getUnit:function(x,y){
        //console.log('search x:'+x+' '+y);
        if(this.tCoordUnit[y] &amp;&amp;  this.tCoordUnit[y][x]){
            return this.tCoordUnit[y][x];
        }
        return null;
    },
    getBuild:function(x,y){
        if(this.tCoordBuild[y] &amp;&amp;  this.tCoordBuild[y][x]){
            return this.tCoordBuild[y][x];
        }
        return null;
    },
    //appelée lors d'un clic gauche sur le canvas (sélection d'une unité/batiment)
    click:function(e){
        //recuperation des coordonnées "tableau" x y
        var x=this.getX(e);
        var y=this.getY(e);
       
        //si l'utilisateur a séléctionné un batiment à construire
        if(this.buildcreation!=''){
           
            //on veririfie ensuite les coordonnées
            //des 4 cases nécéssaires à la construction du batiment
            var ok=1;
            if(!oGame.checkCoordVisible(x,y) || !oGame.checkCoord(x,y) ){
                ok=0;
            }else if(!oGame.checkCoordVisible(x+1,y) || !oGame.checkCoord(x+1,y) ){
                ok=0;
            }else if(!oGame.checkCoordVisible(x,y+1) || !oGame.checkCoord(x,y+1) ){
                ok=0;
            }else if(!oGame.checkCoordVisible(x+1,y+1) || !oGame.checkCoord(x+1,y+1) ){
                ok=0;
            }
           
            if(!ok){
                //si une des cases indisponible, on annule
                return;
            }
           
            //si c'est ok, on efface la selection d'emplacement
            this.buildcreation.clear();
            //on indique à l'unité qui doit construire
            //le batiment à construire
            this.selected.buildOn(this.buildcreation);
           
            //on annule la construction en cours
            this.buildcreation='';
           
            return;
             
        }
       
        //on recherche si il y a quelquechose aux coordonnées
        var oUnit=this.getUnit(x,y);
        var oBuild=this.getBuild(x,y);
       
        //si il y a une unité
        if(oUnit){
            //on selectionne celle-ci
            this.select(oUnit);
        }else if(oBuild){
            //si  il y a un batiment,
            //on le selectionne
            this.select(oBuild);
        }else{
            //sinon on supprime la selection sur le canvas
            console.log('pas trouve');
            this.clearSelect();
        }
    },

Le clic droit

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
//appelé lors d'un clic droit sur le canvas (pour un deplacement d'unité)
clickdroit:function(e){
    //recuperation des coordonnées "tableau" x y
    var x=this.getX(e);
    var y=this.getY(e);

    //recuperation de batiment sur ces coordonnées
    var aBuild=this.getBuild(x,y);

    //si une unité est sélectionnée et que le batiment cliqué est une mine ou du bois
    if(this.selected!='' &amp;&amp; aBuild &amp;&amp; ( aBuild.name=='or' || aBuild.name=='wood' )){
        //on créé une ronde du bois/or vers le QG
        //pour alimenter les ressources Or/bois
       
        //on indique que la destination de cycle c'est la mine d'or ou l'arbre
        this.selected.cycleToX=x;
        this.selected.cycleToY=y;

        //on indique que la provenance du cycle c'est le QG
        this.selected.cycleFromX=QGx;
        this.selected.cycleFromY=QGy;

        //on donne comme cible de deplacement la mine d'or/l'arbre cliqué
        this.selected.setTarget(x,y);
    }else if(!this.isWalkable(x,y)){
        //si la case n'est pas accessible, il ne se passe rien
   
        return false;
    }else if(this.selected!=''){
        //si la case est accessible, on y indique à l'unité d'y aller
        this.selected.setTarget(x,y);
    }

    return false;
},

Le déplacement de souris
On a besoin de verifier certaines choses au déplacement de la souris:
Dans le cas d’une construction, on deplace au dessus de la map, le futur emplacement du batiment, en utilisant un code couleur pour savoir si l’on peut le construire ou non.
On veut également savoir si la souris est à une extremité de la carte pour savoir sil l’on doit scroller la map.

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
mousemove:function(e){
        sDirection='';
        if(this.buildcreation){
            //si une construction est en cours
       
            //on recupere les coordonnées
            var x=this.getX(e);
            var y=this.getY(e);
           
            //on efface la derniere position de la construction théorique
            this.buildcreation.clear();
            //on deplace le plan aux nouvelles coordonnées
            this.buildcreation.x=x;
            this.buildcreation.y=y;
            //on affiche le plan
            this.buildcreation.build();
        }else if(this.getYmouse(e)  (oLayer_map.height-10) &amp;&amp; this.getYmouse(e) &lt; oLayer_map.height-1 &amp;&amp; (this.getXmouse(e)  500)){
            //ici on verifie plusieurs choses pour laisser un passage vers le bas
            //vous pouvez voir ces zones de selection en orange
            //si c'est le cas, scroll de la map vers le bas
            sDirection='down';
        }else if(this.getXmouse(e)  (oLayer_map.width-10)){
            //si les coordonnées x sont superieur à la largeur de la map -10:
            //scroll de la map vers la droite
            sDirection='right';
        }
       
    },

La sauvegarde des coordonées d’une unité/batiment
Il y a 2 écoles pour les jeux: les coordonées et les tableau
L’idée est de savoir où son les unités pour savoir si on clic dessus, si il y a une autre unité à proximité (cas d’une attaque) ou si la zone est accessible.
Dans ce tableau de bord j’ai opté pour le choix du tableau

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
saveUnit:function(oUnit){
    //on recupere les coordonnées de l'unité
    var y=oUnit.y;
    var x=oUnit.x;
   
    //on enregistre dans un tableau indexé
    //les nouvelles coordonnées
    if(!this.tCoordUnit[y]){
        this.tCoordUnit[y]=Array();
    }
    this.tCoordUnit[y][x]=oUnit;
   
    //on rend la zone visible
    this.setVisibility(x,y);
},
saveBuild:function(oBuild){
    //on recupere les coordonnées du batiment
    var y=parseInt(oBuild.y);
    var x=parseInt(oBuild.x);
       
    //on enregistre dans un tableau indexé
    //les nouvelles coordonnées
    if(!this.tCoordBuild[y]){
        this.tCoordBuild[y]=Array();
    }
    if(!this.tCoordBuild[y+1]){
        this.tCoordBuild[y+1]=Array();
    }
    this.tCoordBuild[y][x]=oBuild;
    this.tCoordBuild[y+1][x]=oBuild;
    this.tCoordBuild[y+1][x+1]=oBuild;
    this.tCoordBuild[y][x+1]=oBuild;
   
},

La sélection
Que l’on clic sur une unité ou un batiment, il est toujours plus confortable de voir sa sélection à l’écran
Pour cela, on dessine sur un calque un cadre de couleur

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
clearSelect:function(){
    //on efface le calque
    oLayer_select.clear();
    this.selected='';
    //on efface le bloc du bas
    this.resetNav();
},
select:function(oMix){
    //on enregistre l'unité/batiment
    this.selected=oMix;
   
    //on demande son dessin
    this.drawSelected();
   
    //on affiche la navigation
    oMix.buildNav();
},
drawSelected:function(){
    //on efface le calque
    oLayer_select.clear();
   
    //on dessine un cadre sur un des calques au dimension de l'élement
    oLayer_select.drawRectStroke((this.selected.x-currentX)*widthCase,(this.selected.y-currentY)*heightCase,this.selected.width,this.selected.height,'#880044',3);
},
resetNav:function(){
    getById('nav').innerHTML='';
},

Boucle sur les unités
Régulièrement, on met à jour les coordonnées des unités pour permettre leur déplacement

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
refreshUnit:function(){
       
    //on boucle sur les unités existantes
    for(var i=0;i&lt; this.tUnit.length;i++){
        var oUnit= this.tUnit[i];
       
        //si l&#039;unité doit se rendre quelques part
        if(oUnit.targetX!=&#039;&#039; &amp;&amp; oUnit.targetY!=&#039;&#039; &amp;&amp; (oUnit.targetX!=oUnit.x || oUnit.targetY!=oUnit.y) ){
       
            var vitesse=1;
            var vitesse2=vitesse*-1;
           
            //on efface le dessin sur le calques
            oUnit.clear();
           
            //on initialise les nouvelles coordonnées
            var newX=oUnit.x;
            var newY=oUnit.y;
           
            //on fait evoluer les coordonnées vers la destination
            if(oUnit.targetX!=&#039;&#039; &amp;&amp; oUnit.x  oUnit.targetX ){
                newX-=vitesse;
            }
            if(oUnit.targetY!='' &amp;&amp; oUnit.y  oUnit.targetY ){
                newY-=vitesse;
            }
           
            //on verifie si aux coordonnées cible, il y a un batiment
            var aBuild=this.getBuild(newX,newY);
           
            //si la cible est le QG
            if(aBuild &amp;&amp; aBuild.name=='building'){
                oUnit.x=newX;
                oUnit.y=newY;
               
                //on definit la nouvelle cible
                oUnit.setTarget(oUnit.cycleToX,oUnit.cycleToY);
               
                //si l'unité transportait de l'or
                if(oUnit.or &gt;0){
                    //on ajoute une ressource or
                    this.addRessourceOr(oUnit.or);
                }else   if(oUnit.wood &gt;0){
                    //idem pour le bois
                    this.addRessourceWood(oUnit.wood);
                }

                //on reset les ressources de l'unité
                oUnit.or=0;
                oUnit.wood=0;
           
            //si la cible c'est un arbre et que le compteur est inferieur à N
            }else if(aBuild &amp;&amp; aBuild.name=='wood' &amp;&amp; oUnit.counter = 8){
                //on indique à l'unité qu'elle transporte 10
                oUnit.wood=10;
               
                oUnit.x=newX;
                oUnit.y=newY;
               
                //on remet le compteur à 0
                oUnit.counter=0;
               
                //on redéfinit la nouvelle cible (c'est un cycle)
                oUnit.setTarget(oUnit.cycleFromX,oUnit.cycleFromY);
           
            //si la cible c'est une mine d'or et que le compteur est inferieur à N
            }else if(aBuild &amp;&amp; aBuild.name=='or' &amp;&amp; oUnit.counter = 8){
                //on indique à l'unité qu'elle transporte 10
                oUnit.or=10;
               
                oUnit.x=newX;
                oUnit.y=newY;
               
                //on remet le compteur à 0
                oUnit.counter=0;
               
                //on redéfinit la nouvelle cible (c'est un cycle)
                oUnit.setTarget(oUnit.cycleFromX,oUnit.cycleFromY);
               
            }else if(this.checkCoord(newX,newY)){
                //si la coordonnées est libre
                oUnit.x=newX;
                oUnit.y=newY;
            }else if(this.checkCoord(oUnit.x,newY)){
                //si la coordonnées est libre (cas d'un evitement d'obstacle)
                oUnit.y=newY;
            }else if(this.checkCoord(newX,oUnit.y)){
                //si la coordonnées est libre (cas d'un evitement d'obstacle)
                oUnit.x=newX;
            }else if(this.checkCoord(newX,oUnit.y)){
                //si la coordonnées est libre (cas d'un evitement d'obstacle)
                oUnit.x=newX;
            }else if(this.checkCoord(oUnit.x+vitesse2,oUnit.y)){
                //si la coordonnées est libre (cas d'un evitement d'obstacle)
                oUnit.x=oUnit.x+vitesse2;
            }else if(this.checkCoord(oUnit.x,oUnit.y+vitesse2)){
                //si la coordonnées est libre (cas d'un evitement d'obstacle)
                oUnit.y=oUnit.y+vitesse2;
            }
           
            //on dessine l'unité
            oUnit.build();
           
            console.log('recalcul');
            //on met à jour la partie visible de la carte
            oGame.displayVisibility();
        }
   
    }
   
    //on redessine le cadre de selection
    this.drawSelected();
   
},

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

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

ORM or not ORM faut-il les utiliser ?

Avec l’arrivée des frameworks php, nous avons appris à évoluer entre ces nouveaux acronymes: MVC, DAO, DRY et ORM
Mais qu’est-ce qu’un ORM ?
Un ORM (Object Relation Mapping) est un ensemble de librairies qui vous permettent d’interagir avec vos données via des objets.
La plupart des frameworks respectant le design MVC* intègrent leurs propres ORM plus ou moins performants.
note: pour les exemples de syntaxes d’ORM j’utiliserai l’ORM du mkFramework.

*MVC: Model View Controller (séparation de la couche modèle de l’affichage et du controlleur)

Un ORM pourquoi faire ?
Pourquoi utiliser un ORM plutôt que d’écrire simplement des requêtes SQL dans pdo ?
Si vous voulez coder propre, vous allez dans un premier temps écrire toutes vos requêtes dans un fichier php.
Si vous souhaitez organiser un peu mieux, vous allez regrouper les requêtes par table…
Si vous souhaitez éviter de vous répéter, vous aller créer un fichier/une classe pour les éléments récurrents (connexion, requête…) : principe de DRY*
Le fait de faire ceci, vous avez créé un pseudo début d’ORM ;)

*DRY Don’t Repeat Yourself (créer des fonctions/classes pour éviter d’écrire plusieurs fois le même code)

Un des avantages des ORM se situe concernant la manipulation des données.
Prenons un exemple ou dans plusieurs pages vous devez modifier uniquement le titre, l’auteur ou le nombre de vue d’un article.
En pdo simple vous écririez:

‘UPDATE article SET titre=? WHERE id=?’,$titre,$îd
‘UPDATE article SET auteur=? WHERE id=?’,$auteur,$îd
‘UPDATE article SET nbVue=nbVue+1 WHERE id=?’,$îd

Avec l’ORM vous n’avez pas besoin d’écrire à l’avance vos différents types de requêtes

$oArticle=model_Article::getInstance()->findById($id);
$oArticle->titre=$titre;
$oArticle->save();
//ou
$oArticle->auteur=$auteur;
$oArticle->save();
//ou
$oArticle->nbVue=($oArticle->nbVue+1);
$oArticle->save();

note: vous pouvez, si vous le souhaitez écrire des requêtes update
Exemple:

class model_article extends abstract_model{
(…)
public function updateNbVue($id){
$this->execute(‘UPDATE article SET nbVue=nbVue+1 WHERE id=?’,$id);
}
}

Les avantages sont multiples:

  • Organiser vos requêtes
  • Gagner du temps à l’écriture
  • Bénéficier des avantages du design MVC*

Tous les ORMs n’offrent pas les mêmes performances
Pour argumenter un post dans un topic sur ce même site, j’avais à l’époque procédé au benchmark suivant:
100 000 enregistrements en base de donnée mysql, et l’affichage de ces lignes dans un tableau html.
5 types différents:
– php simple en utilisant pdo
– une application en Zend Framework (version 1.11.12)
– une application en Yii (version yii-1.1.14)
– une application en MkFramework (méthode findMany() )
– une application en MkFramework en utilisant la récupération rapide (méthode findManySimple() )

Résultat des courses:

benchmark

– 0.50s : pdo simple
– 0.58s : MkFramework en utilisant la récupération rapide
– 0.68s : Yii (version yii-1.1.14)
– 1.97s : MkFramework (normal)
– 11.20s : Zend Framework (version 1.11.12):

note : j’ai fait un benchmark sur 100 000 entrées pour avoir un volume assez important pour mettre en évidence les différences de performances, si j’avais fait un findById(4)
Sur un seul enregistrement on aurait
benchmark2

– 0.007s : pdo simple
– 0.015s : MkFramework en utilisant la récupération rapide
– 0.043s : MkFramework (normal)
– 0.049s : Zend Framework

note 2 : pour obtenir de bonnes performances avez yii j’ai écrit les requêtes sans passer par les classes « ORM »

$sql=’SELECT * FROM Article';
$connection=Yii::app()->db; // vous devez avoir une connection « db »
$command=$connection->createCommand($sql);
$articles=$rows=$command->queryAll();

Les ORMs offrent de la flexibilité
J’ai lu à plusieurs reprise que les ORM étaint lourd, qu’ils retournaient toujours toutes les colonnes même ceux non utilisées, que pour faire une jointure on se retrouvait à récupérer une ligne entière sur les deux tables liés…
Premièrement, vous pouvez choisir de récupérer uniquement certains champs de votre table. Beaucoup d’ORM proposent d’écrire sa requête SQL
Par exemple:

class model_article extends abstract_model{
(…)
public function findListAuteurName(){
return $this->findMany(‘SELECT auteur.name FROM auteur’);
}
}

Idem pour les fameuses jointures, vous avez le choix de récupérer par exemple un objet article puis de lui demander de nous retourner son auteur pour afficher son nom ainsi

$oArticle=model_article::getInstance()->findById(4);
print $oArticle->findAuteur()->nom;

Mais vous pouvez également écrire une méthode qui vous retournera les articles avec leur auteurs ;)

class model_article extends abstract_model{
(…)
public function findArticleWithAuteur(){
return $this->findOne(‘SELECT article.titre, auteur.name FROM article INNER JOIN auteur ON article.auteur_id=auteur.id WHERE article.id=?’,(int)$id);
}
}

Une flexibilité aussi concernant les performances: selon les parties de vos sites vous avez besoin de récupérer plus ou moins d’élements et ceci plus ou moins rapidement.
La encore vous avez le choix: si vous devez récupérer un auteur, ses articles et ceci sans trop regarder les performances vous pouvez utiliser la méthode de récupération d’objets riches findMany/findOne
Si au contraire vous souhaitez afficher un bon nombre d’article d’auteurs, ou autre, vous pouvez utiliser la méthode rapide retournant des objets « simple » findManySimple/findOneSimple

Vous avez le choix :)

Conclusion
Comme vous avez pu le lire ici, tous les ORM ne sont pas des usines à gaz, ils sont très pratiques et vous permettent dans l’esprit du MVC de bien organiser votre couche modèle.
Les ORM sont une bonnes choses que vous codiez une petite application ou une application importante: l’essentiel c’est de penser à long terme (la maintenabilité).
Il sera plus simple pour vous ou un collègue d’intervenir sur votre application avec ORM que sans ORM ;)
Certains ORM sont plus ou moins flexible et performant comme les frameworks, mais ne les mettez pas tous dans le même panier, essayez en plusieurs avant de vous faire un avis.

note: je n’ai pas inclus Symfony 2 (pour tester doctrine) dans le benchmark car malgré mes efforts d’optimisation de cache, mode production… je n’arrivais pas à passer sous la barre des 23 secondes :(
Si vous pouvez m’envoyer un exemple d’application affichant ces 100 000 enregistrements en moins de 2 secondes je suis preneur ;)

note2: retrouvez dans mon post d’origine les éléments du benchmarks (sauf celui de yii fait ici pour l’occasion)
http://www.developpez.net/forums/d1252983-2/club-professionnels-en-informatique/actualites/etes-vous-pour-contre-orm-blogueur-invite-a-tenir-baton-par-milieu/#post6847792

Et si tous les navigateurs utilisaient le même moteur

Ajourd’hui, lorsque vous naviguez sur internet, vous ne vous en rendez peut être pas compte mais ce que vous voyez dans votre écran sur chaque site est un compromis entre ce qu’il est possible de faire actuellement et les contraintes imposées par certains navigateurs.

Et oui, même, si vous ne cessez d’entendre parler d’HTML5, de css3, de 3D dans votre navigateur… toutes ces technologies nécessite que votre navigateur les implémente.
Et c’est là que le bas blesse: chaque navigateur et plus exactement, chaque version de navigateur implémente d’une part les technologies actuellement définis et référencées, et de l’autre part certaines technologies* en cours de finalisation.

Quand je parle ici de technologie, je fais un raccourci, ce sont les spécifications qui sont actées ou en cours de finalisation, après, tout le travail se fait dans le developpement et l’évolution des moteurs de rendu des navigateurs.

Il existe actuellement 4 moteurs de rendu majeurs:

  • Webkit (open-source) utilisé par safari et chrome/chromium
  • Gecko utilisé par firefox
  • Trident utilisé par internet explorer
  • Presto utilisé jusqu’il y a peu par Opera

Opera a décidé en février dernier d’abandonner son propre moteur rendu au profit de webkit, justement c’est l’objet de cet article.

Le travail autour du moteur de rendu
Comme vous aurez pu le comprendre, la partie la plus lourdre a developper est le moteur de rendu, c’est de lui que va découler votre experience de navigation, c’est son niveau d’implémentation qui vous permet de bénéficier des avancées actuelles, et c’est encore lui qui va permettre ou au contraire freiner les developeurs webs dans leur créativité ;)

Pourquoi chaque navigateur continue a développer leur propre moteur de rendu ?
Avec le coup de projecteur dont ont bénéficié l’HTML5 et le CSS3 ces derniers temps (notamment avec la chasse au flash mené par Apple), chaque navigateur a investi du temps et de l’argent pour être toujours en tête concernant l’implémentation de ces nouveaux standards, qui je le rappelle ne sont toujours pas finalisé actuellememnt (RC pour le moment).

Ce moteur de rendu est donc un atout qu’il faut préserver, un avantage à mettre en avant. Un plus que mette en avant justement les navigateurs à travers des campagnes de pub, des tests comparatifs…

Un problème en partie lié à la fragmentation de version de navigateur
Si aujourd’hui les dernières versions des navigateurs offre une implémentation quasi similaire des dernières spécifications en cours de finalisation, ce n’est pas du tout le cas sur les précédentes versions qui sont les plus utilisées :(
Qui n’a jamais entendu les critiques proférés concernant ie6… si le problème concernait uniquement ie6 je n’aurait pas écrit cet article, le nouveau vilain petit canard c’est désormais ie7/8 voir 9 pour certaines fonctionnalités.

Le problème c’est qu’autant firefox et chrome se batte quasiment quotidiennement pour sortir une nouvelle version apportant son lot d’implémentation supplémentaire, autant Microsoft tarde à sortir ses versions de navigateurs, et ceci entraîne des véritable gouffre entre chaque moteur de rendu (concernant l’implémentation (HTML5/CSS2et3…)

Pour un developpeur, il doit donc faire un compromis entre ce qu’il pourrait faire (car faisant de la veille) et ce qu’il est contraint de faire au vue du navigateur le plus restreignant de sa cible (avant c’était ie6, désormais ie7)

Quel avantage y gagnerait les editeurs de navigateurs
L’avantage est vous l’aurez compris de bénéficier des efforts d’implémentation du moteur de rendu, c’est de mutualisé les efforts de developpent dans un seul moteur.
Les navigateurs seraient toujours à la pointe de l’implémentation et ceci en investissant beaucoup moins de temps et d’argent.

Et pour les developpeurs ?
Ce serait un cadeau de noël perpetuel : développer une fois, tester sur un navigateur sans se demander si le site s’affichera correctement sur un autre navigateur ;)
Pouvoir jouer avec ses jouets plus rapidement: vous ne pouvez pas imaginez la frustration d’un developpeur web passionné qui d’un coté lit un article/tutoriel sur une nouvelle fonctionnalité, la teste sur son poste en local et se voit répondre par son manager/responsable « c’est bien joli/interessant ton truc mais est-ce que ça marche sous ie7/8 ?? »
Et oui frustrations est bien le mot, heureusement, on peut toujours implémenter ces nouvelles fonctionnalités en interne où on peut souvent imposer un navigateur récent ;)

Et pour les internautes
Ce sont eux les gros gagnants: HTML5/CSS3 permettent aujourd’hui non seulement des fonctionnalités nouvelles, mais également des gains de performances et une amélioration de la navigation.
Par exemple l’équipe de jQuery propose depuis cette année deux branches pour sa célèbre librairie: l’une suportant les anciens navigateurs comme ie6/7 et l’autre supportant à partir de ie9
Ceci donne une librairie allégée des fonctionnalitées que jQuery doit simuler pour palier au retard de certains navigateurs.

Conclusion
Si vous travaillez avec des technologies web, vous avez surement du sourire en lisant ce billet utopique, mais avouons-le cela fait du bien de rêver un peu, non ?