Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Annexe : Carte au format XML

Par gnicos (Nicolas Gauville), le 22 août 2012
Navigation rapide : Sommaire

Rappel : Ceci est une annexe du cours sur les jeux isométriques en HTML5/JS.

Ce cours se base sur les chapitres “pas à pas”, et utilise les fichiers fournis à la fin du huitième chapitre :

Jeu entier (incluant les dossiers “www” et “dev”) : jeu.zip

Moteur isométrique (dossier “dev/src” uniquement) : moteur_iso.zip

A l'inverse, ce cours ne se base pas sur les autres annexes.

1. Une carte XML

Dans ce chapitre, nous allons voir comment créer une carte XML du jeu. Le but va donc être de modifier le jeu crée dans le tutoriel pas à pas, pour que cette fois-ci, la carte soit chargée via un fichier XML externe.

Ce type de carte peut avoir de nombreux avantages, notamment lorsque les cartes sont éditées via un éditeur de carte. Le XML fourni ainsi une solution simple pour permettre à plusieurs langages d'utiliser un même fichier.

Globalement, au niveau de la carte, nous allons donc passer de notre tableau 2D en javascript :

var map = [     [1, 1, 0, 0, 0],
                [0, 0, 0, 0, 0],
                [0, 0, 1, 0, 1],
                [0, 0, 1, 0, 0],
                [0, 0, 0, 0, 0]];

.. à un fichier XML bien séparé, avec le même contenu :

<?xml version="1.0" encoding="utf-8"?>
<map>
        <lines>
                <line>1,1,0,0,0</line>
                <line>0,0,0,0,0</line>
                <line>0,0,1,0,1</line>
                <line>0,0,1,0,0</line>
                <line>0,0,0,0,0</line>
        </lines>
</map>

Nous pouvons, par exemple, créer un dossier “maps”, et placer à l'interieur un fichier “map.xml” contenant cette carte.

2. Solution de développement

Pour réaliser les modifications, nous allons créer une classe “XMLMapLoader”, qui contiendra une unique fonction statique “load”, permettant de charger le fichier, et d'ajouter les tuiles à la carte isométrique.

Le seul problème qu'il reste à traiter : c'est comment assigner les numéros du fichier XML à des tuiles. Pour régler ce problème, nous allons créer un objet qui va permettre de passer de l'un à l'autre, gardant, pour chaque numéro, une fonction renvoyant le contenu de la tuile à créer pour ce numéro.

3. Modification du jeu

Pour commencer, nous pouvons, évidement, supprimer la variable “map”, qui n'est maintenant plus utile. Nous allons, à la place, ajouter l'objet dont nous venons de parler, qui va permettre de passer des numéros du fichier XML à des tuiles. Nous pouvons par exemple déclarer un objet “tile”.

var tiles = {};

Ensuite, nous devons créer une fonction qui renvoie une tuile pour 0, et rien pour 1. Nous pouvons donc avoir ce résultat :

var tiles =
{
     0: function() { return new Tile ( TileType.DRAW, gfx.solTAlea(200,200,128), true );},
     1:null
};

Bien entendu, cet objet pourra être modifié selon les cas, pour permettre d'avoir toutes les tuiles nécessaires. De plus, l'utilisation de numéros n'a rien d'obligatoire.

La seconde modification que nous allons devoir effectuer, c'est au niveau de la méthode “play.onClick”. Jusqu'ici, les tuiles étaient définies grâce à notre double boucle for :

for ( var i = 0; i < 5; i++ )
{
        for ( var j = 0; j < 5; j++ )
        {
                if ( map[i][j] === 0 )
                {
                        myMap.addTile ( new Tile ( TileType.DRAW, gfx.solTAlea(200,200,128), true ), i, j, 0 );
                }
        }
}

Mais ce ne sera plus le cas ! Nous pouvons donc supprimer tout ce passage, et le remplacer par l'utilisation de notre méthode “XMLMapLoader.load”. Nous allons donc envoyer à cette fonction la carte isométrique (variable “myMap”), l'URL de la carte (“maps/map.xml”), et l'objet permettant de passer des numéros aux tuiles (tiles).

Nous avons donc le code suivant :

XMLMapLoader.load ( myMap, "maps/map.xml", tiles  );

Si certains se sont perdus, voici tout de même le code final du fichier “core.js”, avec, maintenant, l'utilisation de notre future fonction “XMLMapLoader.load” :

/**
 * Initialisation.
 */
var gameview = document.getElementById('gameview'), stage;
stage = new Stage(gameview);
Ticker.setFPS(24);
Ticker.addListener(stage);
 
var myMap = null;
var tiles = {0: function(){return new Tile ( TileType.DRAW, gfx.solTAlea(200,200,128), true );}, 1:null};
 
/**
 * Menu.
 */
var play = gfx.play();
play.x = 340;
play.y = 268;
var text = null;
 
 
/**
 * Gestion des déplacements clavier.
 */
function onKeyDown ($e)
{        
        current_tile = myMap.getTileAt ( player.posX, player.posY, 0 );
 
        switch ( $e.keyCode )
        {
                case 37: //Gauche
                        to_tile = myMap.getTileAt ( player.posX, player.posY - 1, 0 );
                        break;
                case 38: //Haut
                        to_tile = myMap.getTileAt ( player.posX - 1, player.posY, 0 );
                        break;
                case 39: //Droite
                        to_tile = myMap.getTileAt ( player.posX, player.posY + 1, 0 );
                        break;
                case 40: //Bas
                        to_tile = myMap.getTileAt ( player.posX + 1, player.posY, 0 );
                        break;
                default:
                        return;
                        break;
        }
 
        if ( to_tile )
        {
                player.posX = to_tile.posX;
                player.posY = to_tile.posY;
 
                myMap.update();
                myMap.removeTile ( current_tile );
 
                if ( myMap.tiles.length === 2 )
                {
                        menu ( "Bravo !" );
                }
        }
        else
        {
                menu ( "Perdu !!" );
        }
}
 
function menu ( $text )
{
        /**
         * On supprime le jeu si besoin.
         */
        if ( $text !== "" )
        {
                /**
                 * Il y a un texte à afficher (gagné ou perdu), donc un jeu a supprimer.
                 */
                stage.removeChild ( myMap );                                                           
                myMap.dispose ();
                document.removeEventListener ( 'keyup', onKeyDown );
 
                /**
                 * On affiche le text.
                 */
                text = new Text($text, "30px Arial Black", "#ffff00");
                text.textAlign = "center";
                text.textBaseline = "top";
                text.x = 408;
                text.y = 228;
                stage.addChild ( text );
        }
 
        /**
         * On affiche le menu.
         */
        stage.addChild ( play );
}
 
play.onClick = function ( $e )
{
        /**
         * On supprime le menu.
         */
        stage.removeChild ( play );
 
        if ( text )
        {
                stage.removeChild ( text );
                text = null;
        }
 
        /**
         * On lance le jeu.
         */
        myMap = new Map ();
        myMap.offsetX = 400;
        myMap.offsetY = 200;
        stage.addChild ( myMap );
 
        player = new Player ( TileType.DRAW, gfx.mur ( 255, 0, 0 ), true );
        myMap.addTile ( player, 3, 3, 1 );
 
        XMLMapLoader.load ( myMap, "maps/map.xml", tiles  );
 
        document.addEventListener ( 'keyup', onKeyDown );
};
 
menu ( "" );

4. La classe XMLMapLoader

Nous allons donc maintenant ajouter le fichier “XMLMapLoader.js” dans le dossier “src”. Pensez à l'ajouter dans le fichier “index.html”. Globalement, il n'y aura ici qu'une fonction “load”, avec les trois arguments vus précédemment. Nous aurons donc la structure suivante :

/**
 * Utilitaire XMLMapLoader
 */
var XMLMapLoader =
{
        /**
         * Fonction load.
         * Map $isoMap : carte du jeu.
         * string $url : URL du fichier à télécharger.
         * object $eqs : Objet assignant les numéros aux tuiles.
         */
        load: function ( $isoMap, $url, $eqs )
        {
        }
};

a) Charger le fichier

Première tâche : charger le fichier XML ! Pour fait cela, j'aurais aimé utiliser PreloadJS (ce qui paraît cohérent, ce tutoriel ayant choisi d'utiliser les bibliothèques de CreateJS), mais à l'heure où j'écris ces lignes, les classes nécessaires au chargement d'un fichier XML apportent encore un certain nombre d'erreurs sur certains navigateurs, pourtant récents. J'ai donc, pour cette raison, dû y renoncer.

Pour charger le fichier XML, nous allons donc utiliser la classe XMLHttpRequest (XHR). Pour commencer, nous allons donc l'instancier :

var xhr = new XMLHttpRequest();

Ensuite, nous allons utiliser la méthode “open”, pour définir l'URL à charger (ici, indiquée par le paramètre $url) et la méthode (ici, nous prendrons “GET”, bien que dans le cas présent, cela ne change rien).

xhr.open('GET', $url);

Ensuite, nous utilisons l'évènement “onreadystatechange” pour détecter le changement d'état :

xhr.onreadystatechange = function()
{
      //Code
};

Nous allons alors vérifier que le fichier est chargé, grâce à la condition suivante :

if (xhr.readyState === 4 && xhr.status === 200)
{
}

Une fois chargé, le contenu du fichier sera donné via la propriété “responseXML” (au format XML, du moins). Si vous voulez le contenu au format texte, vous pouvez utiliser la propriété “responseText”, par exemple, pour vérifier le chargement :

alert ( xhr.responseText );

Vous aurez alors une boite de dialogue avec le contenu du fichier XML.

b) Créer la carte

Notre fichier étant chargé, nous allons maintenant créer notre carte. Pour commencer, le contenu qui nous intéresse, dans le fichier XML, c'est le contenu des balises ”<line></line>”.

<?xml version="1.0" encoding="utf-8"?>
<map>
        <lines>
                <line>1,1,0,0,0</line>
                <line>0,0,0,0,0</line>
                <line>0,0,1,0,1</line>
                <line>0,0,1,0,0</line>
                <line>0,0,0,0,0</line>
        </lines>
</map>

Nous allons donc créer un tableau contenant le contenu de ces balises, grâce à la méthode “getElementsByTagName” :

var lines = xhr.responseXML.getElementsByTagName('line');

Et alors, il est temps de créer notre première boucle for :

for ( var i = 0; i < lines.length; i++ )
{
 
}

A l'intérieur de cette boucle, nous allons donc traiter une ligne, donnée par la variable “lines[i]”. Cette ligne sera donc de la forme “0,0,0,0,0”. Pour aller plus loin, nous allons donc devoir découper cette chaine de caractères pour la transformer en tableau de chiffres. Nous pouvons heureusement le faire très simplement avec la méthode “split” :

var line = lines[i].textContent.split(',');

Nous allons alors pouvoir créer notre deuxième boucle for, parcourant, cette fois, le tableau “line” :

for ( var j = 0; j < line.length; j++ )
{
 
}

Ensuite, nous allons donc pouvoir accéder à la case en cours du tableau, donnée par la variable “line[j]”. Nous allons donc devoir transformer ce numéro en tuile. Nous allons donc utilisé l'objet qui permet de passer de l'un à l'autre, donné dans le paramètre “$eqs” :

var tile = $eqs[line[j]];
Attention : rappelez-vous bien que l'objet $eqs (variable “tiles” dans le fichier “core.js”) donnes des fonctions ! La variable “tile” que nous venons de définir est donc une fonction !

Ensuite, nous devons l'ajouter à la carte, en n'oubliant pas de vérifier qu'elle n'est pas nulle (cas des 1, entre autre), aux coordonnées (i, j, 0) :

if ( tile !== null )
{
        $isoMap.addTile ( tile(), i, j, 0 );
        //On remarque bien les "()", tile étant une fonction, si elle est définie.
}

Enfin, ultime étape, en dehors de notre fonction “xhr.onreadystatechange” : charger le fichier :

xhr.send(null);

c) Récapitulons

Notre classe “XMLMapLoader contiens alors le code suivant :

/**
 * Utilitaire XMLMapLoader
 */
var XMLMapLoader =
{
        /**
         * Fonction load.
         * Map $isoMap : carte du jeu.
         * string $url : URL du fichier à télécharger.
         * object $eqs : Objet assignant les numéros aux tuiles.
         */
        load: function ( $isoMap, $url, $eqs )
        {
                var xhr = new XMLHttpRequest();
                xhr.open('GET', $url);
 
                xhr.onreadystatechange = function()
                {
                        if (xhr.readyState === 4 && xhr.status === 200)
                        {
                                var lines = xhr.responseXML.getElementsByTagName('line');
 
                                for ( var i = 0; i < lines.length; i++ )
                                {
                                        var line = lines[i].textContent.split(',');
 
                                        for ( var j = 0; j < line.length; j++ )
                                        {
                                                var tile = $eqs[line[j]];
                                                if ( tile !== null )
                                                {
                                                        $isoMap.addTile ( tile(), i, j, 0 );
                                                }
                                        }
                                }
                        }
                };
                xhr.send(null);
        }
};

5. Conclusion

Notre jeu charge alors une carte externe au format XML. Cette méthode pourra vous être très utile dans le développement de gros jeux, et cette organisation (classe XMLMapLoader) permet de rendre cette utilisation très souple. En effet, nous verrons dans d'autres annexes d'autres formats de cartes, gérés par d'autres classes, permettant ainsi de passer de l'un à l'autre sans avoir à reconstruire le jeu.

Navigation rapide : Sommaire