Journal de bord: création d’un bomberman-like en HTML5, jour 1

bomber1

Introduction
Lors du précédent journal de bord, j’ai développé un jeu de stratégie temps réel ou RTS multiplayer en HTML5.
J’ai beaucoup appris de ces 17 jours, et j’espère que vous aussi.
Aujourd’hui commence un nouveau journal de bord pour developper un jeu bomberman-like multiplayer également en HTML5.
L’avantage, c’est qu’on ne part pas de zéro, on a déjà une base de structure Map/unité/cycle/serveur multiplayer…
note: comme pour le RTS, ce jeu est disponible sur github, je mets des extraites de code pour exemple mais les classes entières sont disponibles sur le dépôt.

Les bases du jeu
Voici la liste des fichiers du jeu:

  • bombermanM.html
  • bombermanM.js
  • bombermanM_Bomb.js
  • bombermanM_Game.js
  • bombermanM_Map.js
  • bombermanM_Perso.js
  • bombermanM_Sound.js

+ Fichier serveur coté node.js

  • serverBomberM.js

bombermanM.html
Comme pour le RTS: une page html qui charge les différents fichiers javascripts du jeu.
Il y a toujours deux div utilisés pour indiquer le chargement et l’autre pour permettre de choisir sa team.
A une différence: il y a moins de canvas (calques) à gerer: ici layer_map, layer_bomb et layer_perso.

bombermanM.js
Script principal du jeu qui contient également la classe Images permettant de charger et d’identifier les sprites.
Une fonction de preload qui charge le sprite, puis instancie les canvas, le jeu et construit la map.
Ce script contient également la méthode appelé en continue: la fonction run()

bombermanM_Map.js
Cette classe est quasiment la même que celle du RTS à une différence près: il n’y a pas ici de gestion d’aperçu.
Cette classe est constitué d’un constructeur et de 2 méthodes build() et drawImage()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Map.prototype={
    build:function(){
        for(var y=0;y< maxY;y++){
            for(var x=0;x< maxX;x++){
                if(this.tMap[y] && this.tMap[y][x]){
                    //on dessine sur le canvas la valeur du tableau
                    this.drawImage( this.tMap[y][x] ,x,y);
                }
            }  
        }
    },
    //la methode pour dessiner sur le canvas
    drawImage:function(iImg,x,y){
        console.log(this.tImg[iImg]);
        oImages.drawImageOnLayer(this.tImg[iImg],x*widthCase,y*heightCase,widthCase,widthCase,'map');
    },
   
};

bombermanM_Game.js
Cette classe ressemble également beaucoup à celle du RTS, il y a dans le constructeur la partie écoute du socket,

1
2
3
4
5
6
7
8
9
10
11
12
13
socket=io.connect('http://localhost:1338');
   
socket.on('Game.createPerso',function(id, team,name,x,y){
    var oPerso=new Perso(name,team);
    oPerso.x=x;
    oPerso.y=y;
    oPerso.id=id;
    oPerso.build();
   
    console.log('creation perso team:'+team+' x:'+x+' y:'+y);
   
    oGame.tPerso.push(oPerso);
});

puis plusieurs méthodes du jeu comme la récupération d’un personnage,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
getPersoById:function(id){
    for(var i=0;i< this.tPerso.length;i++){
        if(this.tPerso[i].id==id){
            return this.tPerso[i];
        }
    }
},
getPersoByTeam:function(team){
    for(var i=0;i< this.tPerso.length;i++){
        if(this.tPerso[i].team==team){
            return this.tPerso[i];
        }
    }
},

d'une bombe par son id ou ses coordonnées,

1
2
3
4
5
6
7
getBombById:function(id){
    for(var i=0;i< this.tBomb.length;i++){
        if(this.tBomb[i].id==id){
            return this.tBomb[i];
        }
    }
},

La création d'un bombe sur la map

1
2
3
4
createBombBroadcast:function(team,name,x,y){
    console.log('socket create bomb'+team+' '+name+' x:'+x+' y:'+y);
    socket.emit('Game.createBombBroadcast',team,name,x,y);
},

La boucle d'affichage des joueurs:

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
refreshPerso:function(){
       
    //on boucle sur les persos existants
    for(var i=0;i< this.tPerso.length;i++){
        var oPerso= this.tPerso[i];
        if(oPerso.life <=0){ continue;}
       
            var vitesse=0.5;
           
            if(!this.tDirection[oPerso.team]){
                continue;
            }
           
            var sDirection=this.tDirection[oPerso.team];
           
            //on efface le dessin sur le calques
            oPerso.clear();
           
            //on initialise les nouvelles coordonnées
            var newX=oPerso.x;
            var newY=oPerso.y;
           
            //on fait evoluer les coordonnées en fonction de la direction
            if(sDirection=='right'){
                newX+=vitesse;
            }else if(sDirection=='left'){
                newX-=vitesse;
            }
            if(sDirection=='up'){
                newY-=vitesse;
            }else if(sDirection=='down'){
                newY+=vitesse;
            }
           
            if(this.checkCoord(newX,newY)){
                //si les coordonnées est libre
                oPerso.x=newX;
                oPerso.y=newY;
               
            }
           
            //on dessine le personnage
            oPerso.buildBroadcast('walking');
           
       
    }
   
   
},

le choix d'une team

1
2
3
4
5
6
7
8
9
10
11
12
setTeam:function(team){
    this.team=team;
           
    if(team=='blue'){
        setTimeout(run,fps);
    }
   
    getById('team').style.display='none';
           
    map.build();
    this.refresh();
},

Et une chose nouvelle: la prise en compte des touches du clavier:

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
eventKeyDown:function(e){
    var touche = e.keyCode;

    this.resetKeys();
    if(touche==37){
        this.setTeamDirectionBroadcast('left');
    }
    if(touche==38){
        this.setTeamDirectionBroadcast('up');
    }
    if(touche==39){
        this.setTeamDirectionBroadcast('right');
    }
    if(touche==40){
        this.setTeamDirectionBroadcast('down');
    }
   
    if(touche==32){ //espace
        console.log('depot bombe');
        //boucle perso pour savoir ou creer la bombe
        var oPerso=this.getPersoByTeam(this.team);
           
        this.createBombBroadcast(oPerso.team,'normal',oPerso.getX(),oPerso.getY());
           
    }
},

Qui est appelé avec un onkeydown dans la balise body

1
onkeydown="oGame.eventKeyDown(event)"

Parenthèse sur la gestion d’evenement
Dans le RTS, chaque joueur utilisait sa souris pour sélectionner une unité, un batiment puis cliquait sur le jeu pour intéragir.
Par exemple quand il cliquait gauche sur la map, on donnait l’ordre à l’unité d’aller à un endroit, et l’on broadcastait cette cible.
Mais ici on ne joue pas à la souris mais au clavier, ici on ne va pas communiquer des coordonnées cible ou autre, on va broadcaster la direction voulue pour le personnage.
Ainsi tant que l’on maintient une touche de direction, le personnage continue sa course sans accout.

Le dépôt github
Le dépôt https://github.com/imikado/bomberhtml5

Quelques images
bomber1
bomber2

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

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

Ce jour 17 a été utilisé surtout pour corriger les moultes bugs de la version multi player.
Au fur et à mesure de développement multi joueur, je me suis heurté à beaucoup de soucis.

L’effet Larsen
Premièrement: node.js/socket.io, j’avais identifié les actions coté client et coté serveur avec les mêmes noms, exemple: Unit.build.
Je me suis rendu compte d’un effet Larsen: j’emettais avec le client sur id pensant l’envoyer uniquement au serveur, puis coté serveur j’emmetais vers les clients avec le même identifiant pour que chacun mette à jour son écran.
J’ai donc différencier les identifiants client et serveur avec un suffixe « Broadcast »
Ainsi coté client on fait un

1
socket.emit('Unit.buildBroadcast',this.id,x,y);

Coté serveur, on écoute l’evenement et l’on broadcast un évenement différent (sans le suffixe Broadcast)

1
2
3
4
socket.on('oBuild.buildBroadcast',function(id,x,y){
    socket.broadcast.emit('oBuild.build',id,x,y);
    socket.emit('oBuild.build',id,x,y);
});

Et ainsi coté client, on écoute l’evenement pour intéragir avec l’écran du joueur

1
2
3
4
5
6
7
socket.on('oBuild.build',function(id,x,y){
    var oBuild=oGame.getBuildById(id);
    oBuild.clear();
    oBuild.x=x;
    oBuild.y=y;
    oBuild.build();
});

Persistance de la partie
Sur la première version multijoueur, il fallait lancer les deux navigateurs à peu près en meme temps, pour qu’ils se broadcaste l’un l’autre leurs unités respectives.
Mais ceci n’était pas une bonne idée, j’ai donc ajouté plusieurs choses:
Рon stocke sur le serveur deux tableaux: tBuild et tUnit qui stoque les entit̩s (unit̩s et batiments)
– lors de la connection d’un joueur, on boucle sur ces tableaux pour lui afficher l’état actuel de la partie
– pour le debug: 4 boutons pour choisir sa team
Pour info, c’est la team bleu qui mène la danse: c’est ce joueur qui possède la boucle de jeu (cycle de raffraichissement des unités et batiments)

Note: pour sauvegarder les informations de partie, on met à jour les données des tableaux tUnit et tBuild

1
2
3
4
5
6
7
8
socket.on('unit.buildBroadcast',function(id,x,y){
    var i=getUnitById(id);
    tUnit[i].x=x;
    tUnit[i].y=y;
    socket.broadcast.emit('unit.build',id,x,y);
    socket.emit('unit.build',id,x,y);
   
});

La fonction pour récupérer les données de l’unité:

1
2
3
4
5
6
7
function getUnitById(id){
    for(var i=0;i<tUnit.length;i++){
        if(tUnit[i].id==id){
            return i;
        }
    }
}

Le prototype qui sert juste à stoquer les informations d'unité et batiments:

1
2
3
4
5
6
7
8
9
10
function Data(){
    this.team;
    this.name;
    this.id;
    this.x;
    this.y;
};
Data.prototype={
   
};

Copie de Unit.js et Build.js
Pour permettre de gérer plus facilement certains éléments du jeu multiplateforme, j’ai du dupliquer les classes Unit et Build

Modification de la méthode build de la classe Unit pour broadcaster la création d’un batiment: en effet, à la création d’une unité et d’un batiment, il faut avertir les autres joueurs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//si l'unité doit construire un batiment, et qu'elle se trouve sur les lieux de la construction
if(this.oBuildOn && this.getX()+1==this.oBuildOn.x && this.getY()==this.oBuildOn.y){
   
    oGame.createBuildBroadcast(oGame.team,this.oBuildOn.name,this.oBuildOn.x,this.oBuildOn.y);
   
    var aBuild=new Build(this.oBuildOn.name,this.team);
   
    //on reset les propriétés de construction
    oGame.buildcreation='';
    this.buildOnX='';
    this.buildOnY='';
    this.oBuildOn='';
   
    //on décrément la ressource or et bois
    oGame.useRessource(this.team,'or',aBuild.costOr);
    oGame.useRessource(this.team,'wood',aBuild.costWood);
   
    //on réactualise les ressources
    oGame.buildRessource();
    //on reset la sélection
    oGame.clearSelect();
   
}

note: un bug que j’ai mis longtemps à identifier: le mauvais nom d’event, en effet, si vous vous trompez de nom d’évenement à emettre, vous ne le savez pas : pas d’erreur :(
si vous avez un doute, ajoutez des console.log() pour vérifier que vous passer bien dans vos évenements ;)

Voilà pour le résumé des derniers déboires ;)
J’espère que ce journal de bord vous évitera de perdre autant de temps que moi.

Je pense améliorer un peu leu jeu, en ajoutant un menu, permettre de créer une nouvelle partie mais ce sera tout pour ce jeu.

Le prochain jeu sera un bomberman-like multijoueur ;)

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

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

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

Aujourd’hui nous allons décliner ce jeu en une version multiplayer

Pour cela, nous allons utiliser les websockets ainsi qu’un server node.js

Présentation de node.js et socket.io
Node.js est un serveur javascript utilisant le moteur de chrome, plus d’infos sur le site de nodejs: http://nodejs.org/
Socket.io est un serveur temps réel qui permet de transmettre des messages.
Les deux réunis couplés aux nouvelles capacités d’html5 permettent de mettre en place un jeu multi-joueur en temps réel.

Installons le serveur
Sur un serveur ubuntu, ouvrez un terminal et lancez la commande suivante:

1
sudo apt-get install nodejs

Pour les utilisateurs windows et mac, rendez-vous sur le site de nodejs: http://nodejs.org/

Installer ensuite socket.io
Pour cela il faut le binaire npm
Installez le avec la commande

1
sudo apt-get install npm

Puis installer le packet socket.io

1
npm install socket.io

Principe de base du jeu multi-player
Le principe de ce serveur tient dans un concept evenementiel: un client peut emettre un message et également être à l’écoute.
Le serveur est dans le même cas, il peut emettre soit vers l’émetteur (comme une réponse), soit vers tous les autres clients connectés.
On peut ainsi imaginer à chaque déplacement, avertir les autres joueurs, et vice versa.

Mise en place dans notre jeu
Pour ce RTS, il y a plusieurs solution différente et efficace.

Ici on va choisir d’avoir le premier joueur maitre, qui fera tourner le cycle du jeu et diffusera les ordres d’affichage des unités et batiments.
Pour cela, on va ajouter dans les classes Unit et Build, des copie de certains méthodes

1
2
3
buildBroadcast:function(){
    socket.emit('unit.build',this.id,this.x,this.y);
},

Et coté serveur: (nodejs)

1
2
3
4
socket.on('unit.build',function(id,x,y){
    socket.broadcast.emit('unit.build',id,x,y);
    socket.emit('unit.build',id,x,y);
});

On indique également dans le fichier rtsMultiplayer dans la fonction load, de ne créer la sequence que si le joueur est la team « bleu »

1
2
3
 if(oGame.team=='blue'){
        setTimeout(run,fps);
    }

Ainsi, le joueur bleu, va avoir une séquence normale: toutes les N milli secondes, il boucle pour raffraichir, mais au lieu de raffraichir localement, il broadcast à tout le monde les nouvelles positions: c’est un peu l’intelligence du serveur multi-joueur.

Vous avez compris l’idée: chaque methode d’affichage d’unité et batiment est remplacé par une méthode broadcastant ceci
Ansi chaque joueur recoit l’ordre d’afficher l’unité/batiment par le server socket.

J’ai mis à jour le github, mais pour l’instant il y a quelques bugs: on ne peut pas initié de ronde d’or/bois…
On peut déjà connecter plusieurs navigateurs (au moins 2 joueurs) et voir sur chacun d’eux les personnages bouger simultanément.

Je reviendrais ces prochains jours pour corriger ces bugs

Pour lancer le serveur node.js
Dans le repertoire où sont situé les fichiers du github

1
nodejs serverRtsHtml5.js

Ceci lancera le serveur nodejs du jeu

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

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

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

Dans ce jour 15, j’ai ajouté la gestion de la bière: les unités consomment de la bière pour attaquer. Il faut constuire un champ de houblon ainsi qu’une brasserie.
Enfin assigner un ouvrier au houblon pour qu’il fasse des aller retour entre le champ de houblon et la brasserie.
J’en ai également profité pour ajouter un bouton pour activer/désactiver le son.

Ajout des nouveaux batiments: champ de houblon et brasserie
Premièrement on ajoute dans le sprite le champ de houblon ainsi que la brasserie

Enquite on ajoute ce sprite au tableau d’identification
Dans la fonction preload du fichier rts.js

1
2
3
4
5
6
7
8
9
10
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'],
    ['case-houblon','case-houblon_-2','case-houblon_-1'],
    ['build-brasserie','build-brasserie_-2','build-brasserie_-1']
];

Ensuite on ajoute ces deux batiments à la liste constructible par une unité worker:
Dans le constructeur de la classe Unit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
}else if(this.name='Worker'){
    this.shortname='Ouvrier';
    this.src='img3/unit-worker.png';
    this.idImg='unit-worker';
    this.life=100;
    this.attak=5;
   
    this.costOr=100;
    this.costBeer=5;
   
    this.tBuildCreation.push(new Build('SoldierHouse',this.team));
    this.tBuildCreation.push(new Build('ArcherHouse',this.team));
    this.tBuildCreation.push(new Build('Houblon',this.team));
    this.tBuildCreation.push(new Build('Brasserie',this.team));
}

Prise en compte d’un clic droit sur un champ de houblon
Dans la méthode goto de 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
}else if(this.tSelected.length && aBuild && ( aBuild.name=='Houblon')){
    //on créé une ronde du bois/or vers le QG
    //pour alimenter les ressources Or/bois

    for(var i=0;i<this.tSelected.length;i++){

        //on indique que la destination de cycle c'est la mine d'or ou l'arbre
        var cycleToX=x;
        var cycleToY=y;

        //on indique que la provenance du cycle c'est la brasserie
        var cycleFromX=-1;
        var cycleFromY=-1;
       
        for(var j=0;j< this.tBuild.length;j++){
            if(this.tBuild[j].name=='Brasserie'){
                cycleFromX=this.tBuild[j].x;
                cycleFromY=this.tBuild[j].y;
            }
        }
       
        if(cycleFromX < 0){
            alert('Il vous manque une brasserie');
        }
       
        this.tSelected[i].setCycle(cycleToX,cycleToY,cycleFromX,cycleFromY,aBuild.name);

        //on donne comme cible de deplacement la mine d'or/l'arbre cliqué
        this.tSelected[i].setTarget(cycleToX,cycleToY);

Ajout d’un nouveau cycle, celui du houblon
Contrairement aux arbres et à l’or, ici, on ne veut pas que l’unité rapporte la ressource au QG mais à la brasserie.
Pour cela il faut ajouter une condition dans le cas du houblon:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//si la cible c'est du houblon et que le compteur est inferieur à N
}else if(aBuild && aBuild.name=='Houblon' && oUnit.counter = 8  && oUnit.cycleToX!=''){
    //on indique à l'unité qu'elle transporte 1
    oUnit.houblon=1;
   
    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);
                       
    oUnit.animate('walking');

Idem pour la brasserie, il faut la prendre en compte au retour du houblon

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if(aBuild && aBuild.name=='Brasserie' && oUnit.cycleToX!=''){
    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.houblon >0){
        //on ajoute une ressource or
        this.addRessource(oUnit.team,'beer',oUnit.houblon);
    }

    //on reset les ressources de l'unité
    oUnit.houblon=0;

Prendre en compte cette ressource pour l’attaque
L’idée est simple, il faut de la bière aux unités pour pouvoir attaquer, cela donne un nouvel élément de gestion.
Sans bière, peu importe sa force d’attaque l’unité restera statique: elle ne se défendra même pas.
Pour cela, dans la classe Game, méthode refreshUnit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if(iAttack && this.getRessource(oUnit.team,'beer') > oUnit.costBeer ){    

    this.useRessource(oUnit.team,'beer',oUnit.costBeer);
    this.buildRessource();

    oUnit.animate('attack');

    //on decremente l'enemie de la puissance d'attaque
    oUnit2.life-=oUnit.attak;
    if(oUnit2.life <=0){
        oUnit.animate('walking');
        oUnit2.animate('dead');      
        //si unite dead, on l'efface du jeu
        oUnit2.clear();
        oGame.removeUnit(oUnit2);

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

rts20140117

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

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

Dans ce billet peu de choses technique:
Une mise à jour graphique ainsi qu’une amélioration de la fluidité de déplacement

Modification du déplacement de 1 à 0.5
Plutot que de déplacer d’une case à la fois, j’ai modifié la vitesse de déplacement de 1 à 0.5 (multiplié par la largeur d’une case)

Les problèmes à gerer
Cela change plusieurs choses: les coordonnées x et y étant jusqu’à présent utilisées comme les coordonnées du tableau, il a fallu modifier un peu pour que le jeux fonctionne toujours.

Ajout de deux méthodes getX() et getY() dans la classe Unit

1
2
3
4
5
6
getX:function(){
    return parseInt(this.x);
},
getY:function(){
    return parseInt(this.y);
},

Vérification des coordonnées utilisant ces nouvelles méthodes:

1
2
//si l'unité doit se rendre quelques part
}else if(oUnit.targetX!='' && oUnit.targetY!='' && (oUnit.targetX!=oUnit.getX() || oUnit.targetY!=oUnit.getY() ) ){

Modification de certaines méthodes pour prendre en compte la coordonnées entières et non les coordonnées décimales

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;

    },

Voilà, c’est tout pour aujourd’hui

rtsspritesrtsgameplay

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

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

Dans ce jour 13, j’ai modifié la gestion des ressources pour permettre de stoquer l’information par équipe. Ainsi il sera plus simple d’une part de développer une intelligence artificielle qui pourra interagir avec ses ressources comme nos unités et également de faciliter la gestion multi utilisateur.
On ajoute également une gestion de sprite multi-direction.

Le stoquage multi team

1
2
3
4
5
6
7
this.team='blue';
   
//ressources
this.tRessource=Array();
this.tRessource[this.team]=Array();
this.tRessource[this.team]['or']=250;
this.tRessource[this.team]['wood']=150;

L’intéraction avec celles-ci

1
2
3
4
5
6
7
8
9
10
11
addRessource:function(team,ressource,nb){
    this.tRessource[team][ressource]+=nb;
    this.buildRessource();
},
useRessource:function(team,ressource,nb){
    this.tRessource[team][ressource]-=nb;
    this.buildRessource();
},
getRessource:function(team,ressource){
    return this.tRessource[team][ressource];
},

Modifier l’appel par des unités/batiments
On modifie l’appel à la méthode lorsque l’on construit un batiment
En modifiant dans la méthode build de la classe Unit

1
2
3
//on décrément la ressource or et bois
oGame.useRessource(this.team,'or',aBuild.costOr);
oGame.useRessource(this.team,'wood',aBuild.costWood);

Idem pour la classe Build : batiment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
createUnit:function(){
       
    oGame.useRessource(this.unitCreation.team,'or',this.unitCreation.costOr);
    oGame.buildRessource();
   
    var oUnit;
    oUnit =new Unit(this.unitCreation.name,this.team);
   
    oUnit.x=this.x+2;
    oUnit.y=this.y;
    oUnit.build();
   
    oGame.tUnit.push(oUnit);
   
    oGame.displayVisibility();
}

D’ailleurs, on modifie également la construction de la navigation
Pour vérifier les ressources disponible avec cette nouvelle méthode.

1
if(oGame.getRessource(this.team,'or') > this.unitCreation.costOr){

Je profite de cette mise à jour pour assombrir totalement le brouillard de niveau 1.

Permettre de rapporter des ressources hors cycle
Imaginons que vous ayez une unité qui a coupé du bois ou récupéré de l’or, vous cliquez dessus pendant son retour pour lui faire faire autre chose, il serait sympa de pouvoir rapporter cette ressource au QG une fois l’action terminée.
Pour cela, il faut pouvoir cliqué droit sur le QG et permettre aux unités transportant une ressource de s’y rendre pour déposer la ressource.

Pour cela on modifie la méthode clic droit (goto de la classe Game) pour ajouter ce cas

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
}else if(this.tSelected.length && aBuild && ( aBuild.name=='QG' )){
           
    for(var i=0;i0){
        //on ajoute une ressource or
        this.addRessource(oUnit.team,'or',oUnit.or);
    }else   if(oUnit.wood >0){
        //idem pour le bois
        this.addRessource(oUnit.team,'wood',oUnit.wood);
    }

    //on reset les ressources de l'unité
    oUnit.or=0;
    oUnit.wood=0;
   
    oUnit.buildNav();

Ajouter une gestion de sprite de direction
Il serait plus agréable d’avoir l’unité animé en fonction de sa direction, pour cela plusieurs choses:
1. créer des sprites de direction différente
2. choisir le bon sprite en fonction de la direction de déplacement
Dans la partie identification des sprites, on rajoute les différents sprites de direction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[
    'unit-worker',
    'unit-worker_attack',
   
    'unit-worker_walkingRight',
    'unit-worker_walking2Right',
   
    'unit-worker_walkingLeft',
    'unit-worker_walking2Left',
   
    'unit-worker_walkingDown',
    'unit-worker_walking2Down',
   
    'unit-worker_walkingUp',
    'unit-worker_walking2Up'
   
],

Dans la classe Unit on analyse la direction et chargeons ainsi le bon sprite

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
}else if(action=='walking'){
           
    var sDirection='';
    if(this.targetY  this.y){
        sDirection='Down';
    }else if(this.targetX > this.x){
        sDirection='Right';
    }else{
        sDirection='Left';
    }
   
    if(this.tmpIdImg==this.idImg+'_walking2'){
        tmpImg=this.idImg+'_walking'+sDirection;
    }else{
        tmpImg=this.idImg+'_walking2'+sDirection;
    }

rts_sprites

Le jeux actuellement
rts_spritesjeux

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

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

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

Dans ce jour 12, on va gérer des sprites d’animation et en profiter pour réorganiser la gestion du son de manière plus propre.

Ajout d’une méthode animate
On va ajouter une méthode animate qui permettra à la fois de modifier l’image affichée mais également de jouer le bon son

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
animate:function(action){
    //on commence par effacer la zone
    oLayer_perso.clearRect(((this.x-currentX)*widthCase),((this.y-currentY)*heightCase),widthCase-2,widthCase-2);

    var tmpImg;
    if(action=='attack'){
        tmpImg=this.idImg+'_attack';
        this.playSound('attack',this.action);
    }else if(action=='walking'){
        //si le précédent sprite était A
        //on prend le B (pour créer une animation)
        if(this.tmpIdImg==this.idImg+'_walking2'){
            tmpImg=this.idImg+'_walking';
        }else{
            tmpImg=this.idImg+'_walking2';
        }
        //on arrête tout son (on marche)
        this.stopSound();
        //on stoque le sprite affiché pour la prochaine animation
        this.tmpIdImg=tmpImg;
    }else if(action=='dead'){
        this.playSound('dead',this.action);
        return;
    }else if(action=='wood'){
        tmpImg=this.idImg;
        this.playSound('wood',this.action);
    }else if(action=='mining'){
        tmpImg=this.idImg;
        this.playSound('mining',this.action);
    }else if(action=='stand'){
        tmpImg=this.idImg;
    }
   
    oImages.drawImageOnLayer(tmpImg,((this.x-currentX)*widthCase),((this.y-currentY)*heightCase),widthCase-2,widthCase-2,'perso');
   
    this.action=action;
},

Pour recentrer le son sur l’unité, on a du recréé des méthodes de gestion de son que voici:
Note: on stoque la précédente animation afin lorsqu’une animation/action se déroule en plusieurs temps, on lise le son en entier plutot que de saccader celui-ci.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
playSound:function(action,lastAction){
    //si la précédente action était la même, on fait rien
    if(action==lastAction){
        return;
    }
    this.stopSound();
   
    this.oAudio=new Audio();
    this.oAudio.src=oSound.getSrc(action);
    this.oAudio.play();
   
},
stopSound:function(){
    if(!this.oAudio){
        return;
    }
    this.oAudio.pause();
},

Appelez au bon moment cette nouvelle méthode
Après avoir supprimer les précédentes méthodes appellées oSound.resetPlaying() et oSound.stopPlaying()
On doit désormais appelé les bonnes méthodes d’animation pour avoir un état cohérent de chaque unité et éviter de jouer un son inutile.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if(iAttack){    

    oUnit.animate('attack');

    //on decremente l'enemie de la puissance d'attaque
    oUnit2.life-=oUnit.attak;
    if(oUnit2.life <=0){
        oUnit.animate('walking');
        oUnit2.animate('dead');      
        //si unite dead, on l'efface du jeu
        oUnit2.clear();
        oGame.removeUnit(oUnit2);

    }

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

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

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

Dans ce jour 11, on va ajouter un peu de son sur ce jeux qui est bien muet pour l’instant
On ne pas va pas encore mettre l’ambiance, juste 2-3 son histoire d’ajouter un peu de vie

On va ajouté un son lorsqu’une unité coupe du bois, travaille dans la mine, construit un batiment se bat ou meure.
J’ai trouvé ces divers sons sur ce site: http://www.universal-soundbank.com/

Le son en html5
Tout d’abord comment jouer un son en HTML5, comme il y a une classe Image pour créer un objet image, il y a une classe Audio pour crée un objet son.
Vous pouvez ensuite interagir avec ce son: le lire, le mettre en pause…

Nous créons ici dans une classe Sound

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
function Sound(){
   
    this.counter=0;
    this.total=0;
    this.tSrc=Array();
    this.tOAudio=Array();
    this.tPlaying=Array();
}
Sound.prototype={
    resetPlaying:function(){
    this.tPlaying=Array();
    },
    add:function(src,id){
        this.tSrc[this.total]=Array();
        this.tSrc[this.total]['src']=src;
        this.tSrc[this.total]['id']=id;
       
        this.total++;
    },
    load:function(){

        for(var i=0;i< this.total;i++){
        var id=this.tSrc[i]['id'];

        this.tOAudio[id]=new Audio();
        this.tOAudio[id].src=this.tSrc[i]['src'];
        this.tOAudio[id].onload=function(){
           
        };
        this.tOAudio[id].load();
        oSound.counter++;
            preload2();

        }      
    },
    playSound:function(sType){
    console.log("play sound "+sType);
    this.tPlaying[sType]=1;
       //on joue le son
    this.tOAudio[sType].play();
    },
    stopSound:function(sType){
    console.log("stop sound "+sType);
   

    //on remet le son au début
    this.tOAudio[sType].pause();
    this.tOAudio[sType].currentTime=0;
   
    this.tPlaying[sType]=0;
    },
    stopPlaying:function(){
    //return;
    for(var i=0;i< this.total;i++){
        var id=this.tSrc[i]['id'];
        if( !this.tPlaying[ id ] && id!='building'){
        this.stopSound(id);
        }
       
    }
   
    }
   
};

Ajoutons un son de coupe du bois
Il y a deux choses à faire: démarrer le son au début de la coupe du bois avec oGame.playSound(‘wood’);
Et l’arretez une fois le travail terminé avec oGame.stopSound(‘wood’);

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
}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();
        oGame.removeBuild(aBuild);
       
        //on définit un nouvel arbre à prendre en compte
        var tWood=[
            [-1,-1],
            [0,-1],
            [1,-1],
           
            [-1,0],
            [1,0],
           
            [-1,+1],
            [0,+1],
            [1,+1],
        ];
        for(var i=0;i<tWood.length;i++){
            var oBuild2=this.getBuild(aBuild.x+tWood[i][0],aBuild.y+tWood[i][1]);
            if(oBuild2 && oBuild2.name=='wood'){
                oUnit.cycleToX=oBuild2.x;
                oUnit.cycleToY=oBuild2.y;
               
                break;
            }
        }
    }
   
    //on remet le compteur à 0
    oUnit.counter=0;
   
    //on redéfinit la nouvelle cible (c'est un cycle)
    oUnit.setTarget(oUnit.cycleFromX,oUnit.cycleFromY);
                       
    oSound.stopSound('wood');

//si la cible c'est une mine d'or et que le compteur est inferieur à N
}

Faisons de même pour la mine d'or

Ajoutons un son pour le combat
Ici il y a deux sons ici à mettre en place: celui du combat et celui de la mort d’une unité.
Pour le combat à chaque fois qu’on est en position d’attaque (à proximité d’une unité enemie)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if(iAttack){
    oSound.playSound('attack');
                   
    //on decremente l'enemie de la puissance d'attaque
    oUnit2.life-=oUnit.attak;
    if(oUnit2.life <=0){
        //on joue le son d'attaque
        oSound.stopSound('attack');
                           
        //si unite dead, on l'efface du jeu
        oUnit2.clear();
        oGame.removeUnit(oUnit2);
       
        //on joue le son de mort de l'unite
        oSound.playSound('dead');
                               
    }

Dans le cas présent, si l'unité ne finit pas son combat, le son ne sera pas arrêté.
Il faudrait au minimum mettre à zéro en début de boucle de rafraichissement d'unité et stopper en fin de boucle tous les sons sauf ceux en cours
Pour cela on créé une méthode: oSound.resetPlaying();

1
2
3
resetPlaying:function(){
    this.tPlaying=Array();
},

Et en fin de boucle:
oSound.stopPlaying();

1
2
3
4
5
6
7
8
9
10
stopPlaying:function(){
    for(var i=0;i< this.total;i++){
        var id=this.tSrc[i]['id'];
        if( !this.tPlaying[ id ] && id!='building'){
        this.stopSound(id);
        }
       
    }

}

On a ainsi un jeu un peu plus vivant.

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

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

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

Dans ce jour 10, j’ai corrigé pas mal de bug notamment concernant le problème de suppression d’un arbre ou d’une unité
En effet, ces derniers étaient bien supprimés de la variable tCoordBuild/tCoordUnit mais pas du tableau tBuild/tUnit
J’ai également changé le comportement de base des unités: seule les unités enemies cherchent à se rapprocher pour attaquer: c’est d’ailleurs mieux car les ouviers n’ont pas la même capacité d’attaque que les soldats enemies qui font une ronde.

On va ici permettre à une unité qui coupe du bois, une fois que l’arbre n’existe plus d’aller en chercher un autre à proximité

note: pour rappel, le projet est toujours disponible sur github, dans les billets sur developpez je détaille des parties que je considère interressante, mais je ne détaille pas forcément certains bugfix ou réorganisation du code (pour le rendre plus propre)
N’hésitez pas à suivre le projet github (lien en bas du billet)

Trouver un autre arbre à proximité
On va dès l’instant que l’arbre est supprimé d’en trouver un autre et de mettre à jour la ronde de 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
}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();
        oGame.removeBuild(aBuild);
       
        //on définit un nouvel arbre à prendre en compte
        var tWood=[
            [-1,-1],
            [0,-1],
            [1,-1],
           
            [-1,0],
            [1,0],
           
            [-1,+1],
            [0,+1],
            [1,+1],
        ];
        for(var i=0;i<tWood.length;i++){
            var oBuild2=this.getBuild(aBuild.x+tWood[i][0],aBuild.y+tWood[i][1]);
            if(oBuild2 && oBuild2.name=='wood'){
                oUnit.cycleToX=oBuild2.x;
                oUnit.cycleToY=oBuild2.y;
               
                break;
            }
        }
    }
   
    //on remet le compteur à 0
    oUnit.counter=0;
   
    //on redéfinit la nouvelle cible (c'est un cycle)
    oUnit.setTarget(oUnit.cycleFromX,oUnit.cycleFromY);

Arretez son cycle bois si plus d’arbre
On a vu précédement que dans le cas où l’on a terminé de couper l’arbre il cherche à proximité pour changer d’arbre.
Nous rajoutons maintenant le cas où il n’en trouve pas pour cesser son cycle bois.

1
2
3
4
5
6
7
8
9
10
11
12
13
}else if(oUnit.cycleFromX!='' && (oUnit.targetX==oUnit.x || oUnit.targetY==oUnit.y) ){
   
    //si arrivee a destination et cycle
    if(oUnit.cycleObject=='wood'){
        //si il arrive a venir sur les coordonnées cible
        //c'est que l'arbre n'y est plus
        oUnit.clearCycle();
    }else if(oUnit.cycleFromX==oUnit.x && oUnit.cycleFromY==oUnit.y ){
        oUnit.setTarget(oUnit.cycleToX,oUnit.cycleToY);
    }else{
        oUnit.setTarget(oUnit.cycleFromX,oUnit.cycleFromY);
    }
}

Donner un prix aux unités comme pour les batiments
Jusqu’à présent on pouvait créer autant d’unité que l’on voulait, désormais chaque unité à un prix, comme pour les batiments
D’abord dans la classe Unit, on ajoute un cout dans le constructeur.

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
if(this.name=='Soldier'){
    this.shortname='Soldat';
    this.src='img3/unit-soldier.png';
    this.idImg='unit-soldier';
    this.life=150;
    this.attak=20;
   
    this.costOr=200;
   
}else if(this.name=='Archer'){
    this.shortname='Archer';
    this.src='img3/WC.png';
    this.idImg='unit-archer';
    this.life=100;
    this.attak=30; 
   
    this.costOr=500;
}else if(this.name='Worker'){
    this.shortname='Ouvrier';
    this.src='img3/unit-worker.png';
    this.idImg='unit-worker';
    this.life=50;
    this.attak=5;
   
    this.costOr=100;
   
    this.tBuildCreation.push(new Build('SoldierHouse',this.team));
    this.tBuildCreation.push(new Build('ArcherHouse',this.team));
}

Ensuite dasn la fonction de création de navigation de la classe Build, on ajoute le cout et on indique que si la ressource n’est pas suffisante, on ne peut plus créer 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
45
46
47
buildNav:function(){
    var sHtml='';
   
    sHtml+='<h1>'+this.shortname+'</h1>';
    sHtml+='<p><img src="'+this.src+'"></p>';

   
    if(this.unitCreation){
       
        var sImg='';
   
        sHtml+='<h2>Cr&eacute;ation unit</h2>';
       
        sHtml+='<table><tr>';
       
        sHtml+='<td>';
       
        if(oGame.iOr &gt; this.unitCreation.costOr){

       
            sImg='';
            sColor='#fff';
           
        }else{
           
            sImg='';
           
            sColor='#333';
        }
       
        sHtml+='<td style="background:#444;color:'+sColor+';padding:4px 8px">';
                       
        sHtml+=sImg;
   
        sHtml+='<br />';
        sHtml+='<span style="border:1px solid gray;background:yellow">&nbsp;&nbsp;&nbsp;&nbsp;</span> ';
        sHtml+=this.unitCreation.costOr;
       
           
        sHtml+='</td>';
       
        sHtml+='</tr></table>';
       
    }

    getById('nav').innerHTML=sHtml;
},

rts_12janv

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

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

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

Gérer un mouse over
Il serait utile de pouvoir, au survol afficher un curseur particulier en fonction de l’unité/batiment présent sous la souris

Après avoir ajouté une variable onMouseOver à la classe Game, on va indiquer dans la méthode mousmove quand l’initialiser à 1

1
2
3
}else if(this.mouseX &gt; 0 &amp;&amp; this.mouseX  0 &amp;&amp; this.mouseY &lt; oLayer_map.height){
    this.onMouseOver=1;
}

On ajoute également dans la méthode refreshunit de dessiner la selection sous la souris

1
2
//on dessine le mouseover
this.drawMouseOver();

Qui ressemble à ceci:

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
drawMouseOver:function(){
    oLayer_cursor.clear();
    if(this.onMouseOver==0){
        return;
    }
   
    if(!this.checkCoordVisible(this.mouseCoordX,this.mouseCoordY)){
        return false;
    }

    var oBuild=this.getBuild(this.mouseCoordX,this.mouseCoordY);
    var oUnit=this.getUnit(this.mouseCoordX,this.mouseCoordY);
   
    var sColor=&#039;#ffffff&#039;;
   
    if(oBuild){
        if(oBuild.name==&#039;or&#039;){
            sColor=&#039;yellow&#039;;
        }else if(oBuild.name==&#039;wood&#039;){
            sColor=&#039;#01ad4e&#039;;
        }else if(oBuild.team!=this.team){
            sColor=&#039;red&#039;;
        }
        this.strokeMouseOver(oBuild,sColor);
    }else if(oUnit){
        if(oUnit.team!=this.team){
            sColor=&#039;red&#039;;
        }
        this.strokeMouseOver(oUnit,sColor);
    }
},

Ajout de la gestion d’équipe
On va ajouter une propriété team à la classe Build et Unit afin de différencier ses unités et les unités enemies.

On modifie également le process pour que le brouillard ne soit éclairci que par les unités de notre équipe

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
    //que si c'est une unité de notre équipe
    if(oUnit.team==this.team){
        this.setVisibility(x,y);
    }
},

Ajout de gestion d’attack
Pour l’instant on va gerer l’attaque si une unité est à proximité, on va également indiquer de se rapprocher si une unité enemie est à proxmimité

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
//recherche si unité à porté d'attaque
var tAttak=[
            [-1,-1],
            [0,-1],
            [1,-1],
           
            [-1,0],
            [1,0],
           
            [-1,+1],
            [0,+1],
            [1,+1],
];
for(var j=0;j&lt;tAttak.length;j++){
    var oUnit2=this.getUnit(oUnit.x+tAttak[j][0],oUnit.y+tAttak[j][1]);
    //si unité enemie
    if(oUnit2 &amp;&amp; oUnit2.team!=oUnit.team){
        //on decremente l&#039;enemie de la puissance d&#039;attaque
        oUnit2.life-=oUnit.attak;;
        if(oUnit2.life &lt;=0){
            //si unite dead, on l&#039;efface du jeu
            oUnit2.clear();
        }
        break;
    }
}


//recherche si unité adverse à proximitée
var tMove=[
            [-2,-2],
            [-1,-2],
            [0,-2],
            [1,-2],
            [2,-2],

            [-2,-1],
            [-1,-1],
            [0,-1],
            [1,-1],
            [2,-1],
           
            [-2,0],
            [-1,0],
            [1,0],
            [2,0],
           
           
            [-2,+1],
            [-1,+1],
            [0,+1],
            [1,+1],
            [2,+1],
           
            [-2,+2],
            [-1,+2],
            [0,+2],
            [1,+2],
            [2,+2],
];
for(var j=0;j&lt;tMove.length;j++){
    var oUnit2=this.getUnit(oUnit.x+tMove[j][0],oUnit.y+tMove[j][1]);
    //si unité enemie
    if(oUnit2 &amp;&amp; oUnit2.team!=oUnit.team){
        //l&#039;unité se rapproche pour attaquer
        oUnit.setTarget(oUnit2.x,oUnit2.y)
        break;
    }
}

}

Brouillard de deuxième niveau
Un essai d’ajout de brouillard de deuxième niveau qui permet de voir qu’autour des unités

rts_brouillard2

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