Journal de création d'un bomberman multiplayer en HTML 5
Un récit quotidien de Michael Bertocchi

Le , par imikado, Rédacteur
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()

Code javascript : Sélectionner tout
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,
Code javascript : Sélectionner tout
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,
Code javascript : Sélectionner tout
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,
Code javascript : Sélectionner tout
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
Code javascript : Sélectionner tout
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:
Code javascript : Sélectionner tout
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'); 
  
  
    } 
  
  
},

La suite dans le billet http://blog.developpez.com/ducodeetd...n-html5-jour-1


Vous avez aimé cette actualité ? Alors partagez-la avec vos amis en cliquant sur les boutons ci-dessous :


 Poster une réponse

Avatar de imikado imikado - Rédacteur https://www.developpez.com
le 26/01/2014 à 23:43
Introduction
Ce billet fait suite au billet: http://blog.developpez.com/ducodeetd...n-html5-jour-1

Ici nous allons voir les classes des personnages et des bombes

bombermanM_Perso.js
Ici la classe de personnage qui va permettre d’afficher chacun des personnages sur la map du jeu.
Elle permet également de gérer son animation: pour simuler la marche de celui-ci en direction de la touche de curseur pressée.
Regardons de plus près la méthode build:
Code javascript : Sélectionner tout
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
build:function(){ 
  
    var sDirection=oGame.tDirection[this.team]; 
  
    //si pas de direction, on affiche l'image statique   
    if(sDirection==''){ 
        tmpImg=this.idImg; 
    //si l'image précédente était marche 2, on affiche marche 1 
    }else if(this.tmpIdImg==this.idImg+'_walking2'+sDirection){ 
        tmpImg=this.idImg+'_walking'+sDirection; 
    //sinon on affiche la marche 2 
    }else{ 
        tmpImg=this.idImg+'_walking2'+sDirection; 
    } 
    this.tmpIdImg=tmpImg; 
  
    //partie affichage de l'image du personnage sur le canvas 
    oImages.drawImageOnLayer(tmpImg,(this.x*widthCase)-20,(this.y*heightCase)-20,widthCase,widthCase,'perso'); 
  
    //on affiche ici un carré représentant la couleur de la team 
    oLayer_perso.fillRect((this.x*widthCase),(this.y*heightCase)-25,8,8,this.team); 
  
    //on enregistre les nouvelles coordonnées du joueur 
    oGame.savePerso(this); 
  
},
Comme vous pouvez le voir, on récupère la direction du personnage qui est déduite dans la classe Game (lors du pressage de la touche)
Si aucune direction: le joueur attend, on affiche l’image statique (de face), sinon il y a une direction d’engagée.
Dans ce cas on gère une animation de marche: pour cela on a par direction 2 images, et on va les alterner à chaque affichage du personnage.
Vous voyez également qu’on affiche un petit carré en haut à gauche du personnage pour signaler son équipe.

bombermanM_Bomb.js
Une nouvelle classe fait ici son apparition afin de gérer des objets « éphémères »: les bombes: en effet, à partir de leur création, elles ont une durée limitée sur la map.
De plus, elles doivent à la fin exploser: créer une image de flamme sur plusieurs cases adjacentes et supprimer les eventuels joueurs sur le passage.

Premièrement, au niveau de l’affichage des bombes, on va gérer une animation:
Code javascript : Sélectionner tout
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
refreshBomb:function(){ 
  
    for(var i=0;i< this.tBomb.length;i++){ 
        var oBomb= this.tBomb[i]; 
        if(oBomb.life < 14){ 
            //pendant 14 iterations, on va alterner entre deux sprites 
            if(oBomb.life % 2 ){ 
                oBomb.idImg='bomb-0'; 
            }else{ 
            oBomb.idImg='bomb-1'; 
            }    
        }else if(oBomb.life < 17){ 
            //puis animation d'explosion 
            if(oBomb.life % 2 ){ 
                oBomb.idImg='explosion'; 
            }else{ 
                oBomb.idImg='explosion-1'; 
            } 
        }else if(oBomb.life < 19){ 
            oBomb.idImg='explosion-2'; 
        }else{ 
            oBomb.idImg='explosion-finish';            
        } 
  
        oBomb.life++; 
  
        //on broadcast l'animation de la bombe 
        oBomb.animateBroadcast(oBomb.idImg); 
    } 
  
},
Ensuite au niveau de la classe bombe, on gère l’affichage
Code javascript : Sélectionner tout
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
build:function(){ 
  
    if(this.idImg=='explosion' || this.idImg=='explosion-1' || this.idImg=='explosion-2'){ 
        for(var i=-2;i< 3;i++){ 
            oLayer_bomb.clearRect(((this.x+i)*widthCase),(this.y*heightCase),widthCase,widthCase); 
            oLayer_bomb.clearRect(((this.x)*widthCase),((this.y+i)*heightCase),widthCase,widthCase); 
  
            if(map.tMap[this.y][this.x+i]==1){ 
                oImages.drawImageOnLayer(this.idImg,((this.x+i)*widthCase),(this.y*heightCase),widthCase,widthCase,'bomb'); 
            } 
            if(map.tMap[this.y+i][this.x]==1){ 
                oImages.drawImageOnLayer(this.idImg,((this.x)*widthCase),((this.y+i)*heightCase),widthCase,widthCase,'bomb'); 
            } 
        } 
  
    }else if(this.idImg=='explosion-finish'){ 
        for(var i=-2;i< 3;i++){ 
            oLayer_bomb.clearRect(((this.x+i)*widthCase),(this.y*heightCase),widthCase,widthCase); 
            oLayer_bomb.clearRect(((this.x)*widthCase),((this.y+i)*heightCase),widthCase,widthCase); 
  
            var oPersoVictim=oGame.getPerso(this.x+i,this.y); 
            if(oPersoVictim){ 
                oGame.removeBroadcastPersoById(oPersoVictim.id); 
                console.log('remove '+oPersoVictim.id); 
            } 
            oPersoVictim=oGame.getPerso(this.x,this.y+i); 
            if(oPersoVictim){ 
                oGame.removeBroadcastPersoById(oPersoVictim.id); 
                console.log('remove '+oPersoVictim.id); 
            } 
  
        } 
  
        oGame.removeBroadcastBombById(this.id); 
        return; 
    }else{ 
        oLayer_bomb.clearRect((this.x*widthCase),(this.y*heightCase),widthCase,widthCase); 
    } 
  
    oImages.drawImageOnLayer(this.idImg,(this.x*widthCase),(this.y*heightCase),widthCase,widthCase,'bomb'); 
  
    oGame.saveBomb(this); 
  
},
Gestion intersection
Afin d’eviter d’avoir un bug graphique de chevauchement: lorsqu’un joueur est à cheval entre deux cases, il ne peut pas descendre/monter au risque de « marcher sur un mur »
Pour cela, dans la gestion du déplacement, on verifie que l’on est pas à cheval:

La suite dans le billet: http://blog.developpez.com/ducodeetd...n-html5-jour-2

[ATTACH]135553d1/a/a/a" />
Avatar de imikado imikado - Rédacteur https://www.developpez.com
le 27/01/2014 à 23:30
Introduction
Ce billet fait suite au billet: http://blog.developpez.com/ducodeetd...n-html5-jour-2

Aujourd’hui nous allons corriger quelques bugs du jeu rencontré lors des premiers essai avec des collègues.

Menu affichage des team (en fonction des team disponibles)
Permettre de déposer une bombe pendant que le joueur court
Bugfix concernant le haut de la map et la partie gauche
Mise a jour affichage des teams disponibles
Lorsque les joueurs rejoignent la partie, il leur faut choisir une équipe bleu,rouge,jaune ou vert.
On va mettre à jour cette liste à chaque fois qu’un joueur choisit une équipe ou quitte la partie

Du coté client, on va, au moment où l’on choisit une équipe, indiquer au serveur la team choisie pour qu’il l’enregistre
Coté client:
Code javascript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
setTeam:function(team){ 
    this.team=team; 
  
    if(team=='blue'){ 
        setTimeout(run,fps); 
    } 
  
    getById('team').style.display='none'; 
  
    map.build(); 
    this.refresh(); 
  
    socket.emit('setTeamBroadcast',team); 
},
Coté serveur
Code javascript : Sélectionner tout
1
2
3
4
5
6
7
8
socket.on('setTeamBroadcast',function(team){ 
    tTeamConnected[team]=1; 
    tSocket[socket.id]=team; 
  
    var tTeamConnectedB=getTeamConnected(); 
  
    socket.broadcast.emit('Game.listTeam',tTeamConnectedB); 
});
Ensuite du coté du serveur, on va indiquer que lors de la connection on va broadcaster les team actives
Code javascript : Sélectionner tout
1
2
3
4
5
6
7
8
socket.on('setTeamBroadcast',function(team){ 
    tTeamConnected[team]=1; 
    tSocket[socket.id]=team; 
  
    var tTeamConnectedB=getTeamConnected(); 
  
    socket.broadcast.emit('Game.listTeam',tTeamConnectedB); 
});
Et la fonction getTeamConnected()
Code javascript : Sélectionner tout
1
2
3
4
5
6
7
8
socket.on('disconnect', function () { 
    tTeamConnected[ tSocket[socket.id] ]=0; 
  
    var tTeamConnectedB=getTeamConnected(); 
  
    socket.emit('Game.listTeam',tTeamConnectedB); 
    socket.broadcast.emit('Game.listTeam',tTeamConnectedB); 
});
On appelle la fonction d’affichage du menu des teams disponibles:
Code javascript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
socket.on('Game.listTeam',function(tTeamConnected){ 
  
    for(var i=0;i<tTeam.length;i++){ 
        var a=getById(&#039;button-&#039;+tTeam[i]); 
        if(a){ 
            a.style.display=&#039;block&#039;; 
        } 
    } 
  
    for(var i=0;i< tTeamConnected.length;i++){ 
        console.log(&#039;desactivation &#039;+tTeamConnected[i]); 
        var a=getById(&#039;button-&#039;+tTeamConnected[i]); 
        if(a){ 
            a.style.display=&#039;none&#039;; 
        } 
    } 
  
});
La suite dans le billet: http://blog.developpez.com/ducodeetd...n-html5-jour-3
Avatar de imikado imikado - Rédacteur https://www.developpez.com
le 29/01/2014 à 8:58
Introduction
Ce billet fait suite au billet: http://blog.developpez.com/ducodeetd...n-html5-jour-3

Aujourd’hui on va passer l’intelligence du jeu coté serveur, on va arrêter le concept du joueur bleu qui pilote la partie.
Pour cela, on va alléger la partie client qui ne devrait à terme pouvoir uniquement effacer, afficher, animer un personnage, une bombe (demandé par le serveur).
Le client va également indiquer au serveur les touches qu’il appuie ou qu’il relache (pour que le serveur puisse déduire la direction, et les coordonnées des bombes à placer)

Sur le github habituel vous pouvez voir pas mal de mise à jour en conséquence.

Duplication des classes Game,Perso et Bomb
Pour cela, il nous faut simplifier les classes coté client, et modifier un peu celles coté serveur.
Avant, lorsqu’un joueur été déplacé, le joueur bleu indiquait au serveur la modification à faire pour que celui-ci la broadcast.
Maitenant, vu que c’est le serveur qui gère l’ensemble: il sait que le joueur se déplace et le broadcast directement.

Parenthèse Node.js
Quand j’ai commencé cette migration, je me suis dit que ce serait simple simplement ajouté des inclusions dans le fichier server, un chose que je ne savais pas c’est qu’apparement la porté des variables n’est pas la même
Une chose à faire par exemple quand vous importez une classe dans node.js, il faut penser dans celle-ci à l’exporter
Par exemple: (dans serverBomberM.js)
Code javascript : Sélectionner tout
1
2
  
var Perso=require('./bombermanM_PersoB.js');
Et dans le fichier de la classe bombermanM_PersoB.js (en fin de fichier)
Code javascript : Sélectionner tout
1
2
  
module.exports=Perso;
Je vous invite à attendre la fin de cette migration pour lire les fichiers sur github, je ferais un billet quand ce sera fini.

Le dépot Github:
https://github.com/imikado/bomberhtml5

Le billet: http://blog.developpez.com/ducodeetd...n-html5-jour-4
Avatar de imikado imikado - Rédacteur https://www.developpez.com
le 05/02/2014 à 9:04
Introduction
Ce billet fait suite au billet: http://blog.developpez.com/ducodeetd...n-html5-jour-4

Plusieurs choses dans ce billet:
Premièrement je souhaiterais rappeler comment faire fonctionner le jeu (suite à une question sur github).
Deuxièmement je vais faire un petit point sur la séparation du code client / serveur
Enfin j’indiquerais les prochaines améliorations à venir

Comment faire fonctionner cette application
Cette application HTML5 se divise en 2 parties: la partie cliente : qui sera utilisée par les joueurs et la partie serveur qui non seulement fera tourner le jeu mais broadcastera également l’evolution du jeu à tous les joueurs.

Partie serveur
Pour la partie serveur, on utilise ici 2 choses: node.js et socket.io qui interprète le fichier « serverBomberM.js »
Pour le lancer
Code : Sélectionner tout
nodejs serverBomberM.js
Cette ligne de commande doit lancer le serveur et ne doit pas rendre la main, il rend la main qu’en cas d’erreur
Cette partie serveur fait donc tourner le jeu, et écoute le port 1338
Il recalcule à intervale régulier la position de chaque joueur, bombes et gère les animations de ceux-ci + diffuse à chaque joueur des commandes pour mettre à jour la partie sur son navigateur.

Partie cliente
Cette partie là a été épurée, désormais elle contient uniquement la partie écoute d’action socket et dessin de la partie à l’écran + une partie pour envoyer ses commandes au serveur (action de presser une touche de clavier ou de la relacher)
Elle ouvre 2 choses sur le serveur:

le fichier socket.io qui est diffusé par le serveur http://votreserveur:1338/socket.io/socket.io.js
elle ouvre une connection socket (sur le meme port) http://votreserveur:1338

Avancement de la séparation client/serveur
Le chantier a bien avancé, la séparation est quasiment finie, j’aimerais vraiment au maximum épurée la partie cliente avant de commencer les prochaines mises à jour.
Cela permettra de vraiment tout gerer coté serveur node.js

Améliorations à venir
Le jeu est déjà jouable, mais il lui manque des choses de base comme le fait de ne pas avoir de Game Over, gérer plusieurs parties, il manque également des éléments de jeux comme les murs destructibles, et les bonus d’amélioration de bombes: (plus grand périmètre d’explosion), bombes uniquement horizontal/vertical…
Bref il y a matière à améliorer cette base pour avoir une jeu sympa et envisager de le mettre en ligne

Voila pour ce jour 5, ce projet est un peu en pause actuellement car je profites de ce que j’ai appris pour faire une autre application en parallèle, dont je fais également un journal de bord
Pour les curieux: http://www.developpez.net/forums/d14...cation-dessin/

Le billet: http://blog.developpez.com/ducodeetd...n-html5-jour-5
Offres d'emploi IT
Chef de projet MOA/MOE [H/F]
Matelli - Ile de France - Paris (75003)
CONCEPTEUR DEVELOPPEUR GENESYS H/F
Alten - Nord Pas-de-Calais - NORD
Chef de projet informatique Junior #stage #startup
Tekoway - Provence Alpes Côte d'Azur - Marseille

Voir plus d'offres Voir la carte des offres IT
Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -