Code source
Exécutable de l'éditeur de niveaux (Windows 32bits)
Exécutable du jeu - exactement le même que la partie 4 (Windows 32bits)
Avant d'essayer de compiler le code, allez dans l'onglet "Projets" dans le menu de gauche, séléctionnez l'onglet "Run" pour votre kit,
et mettez dans "Working directory" le chemin de "editor\data" pour l'éditeur ou "game\data" pour le jeu.
Qu'est-ce qu'il nous manque ?
Maintenant que nous avons un éditeur de niveaux où on peut tracer des murs, que nous avons un projet pour le jeu, et que nous avons des graphismes
pour les murs, est-ce qu'on peut commencer à afficher une simple map en 3D ?
Il nous manque juste une étape avant ça: un moyen de sauvegarder la map dans l'éditeur et de la charger dans le jeu.
De plus, ça sera aussi utile que l'éditeur puisse charger des maps qu'il a sauvgardées.
Nous allons mettre ces 2 fonctions dans "Map.cpp" qui est dans le répertoire "common", pour que les 2 projets puissent y accéder.
Même si le jeu lui-même n'utilisera pas la fonction de sauvegarde, c'est une bonne idée de les garder ensemble parce qu'elles sont symétriques.
Quand on écrit un nouveau format de fichier, c'est habituellement mieux de commencer avec une version ASCII qui peut être facile à lire dans un éditeur de texte.
Mais le format des maps est assez simple pour le moment pour qu'on puisse l'écrire en binaire directement.
Dans cette partie, on va se concentrer sur le chargement et la sauvegarde dans l'éditeur. Ensuite, le chargement de la map dans le jeu se réduira à un simple appel
à la fonction de chargement...
Dans "main.qml", j'ai ajouté deux objets FileDialog (un pour le chargement et un pour la sauvegarde) qui ressemblent à ça:
FileDialog {
id: saveDialog
visible: false
title: "Choisissez un fichier"
folder: "file:///maps"
nameFilters: [ "Fichiers map (*.map)", "Tous les fichiers (*)" ]
selectExisting: false;
onAccepted: {
bridge.save(fileUrl);
visible=false;
}
onRejected: visible=false;
}
La seule différence entre les 2 ( à part la fonction appelée dans onAccepted) est la propriété "selectExisting" qui es t mise à "true"
pour le dialog de chargement.
Quand "visible" est mis à true, ces dialogs affichent un sélecteur de fichier standard:
Un peu plus bas dans main.qml, J'ai modifié le menu "Fichier":
Menu {
title: qsTr("Fichier")
MenuItem {
text: qsTr("Charger")
onTriggered: loadDialog.visible = true;
}
MenuItem {
text: qsTr("Sauver")
onTriggered: saveDialog.visible = true;
}
MenuItem {
text: qsTr("Quitter")
onTriggered: Qt.quit();
}
}
Donc maintenant, il ressemble à ça:
Dans "bridge.cpp" il y a 2 nouvelles fonctions:
void CBridge::save(QString fileName)
{
// [8] pour retirer "file:///"
map.save(&fileName.toLocal8Bit().data()[8]);
}
void CBridge::load(QString fileName)
{
// [8] pour retirer "file:///"
map.load(&fileName.toLocal8Bit().data()[8]);
updateQMLImage();
}
Comme le commentaire le dit, il y a une petite astuce ici, parce que les dialogs qml ne nous renvoient pas un chemin, mais une URL qui commence par "file:///".
Par exemple, on recevra: "file:///E:/prg/DungeonMaster/editor/data/maps/test.map"
C'est pour ça que vous pouvez voir un "&" devant "fileName" et "[8]" à la fin, parce que l'on saute 8 caractères.
Dans "Map" j'ai ajouté 2 fonctions "load()" et "save()".
Voici celle qui sauve la map:
char gFileHeader[] = "DMMP"; // Dungeon Master MaP
#define CURRENT_VERSION 1
#define CURRENT_REVISION 0
void CMap::save(char* fileName)
{
FILE* handle = fopen(fileName, "wb");
uint16_t ver = CURRENT_VERSION;
uint16_t rev = CURRENT_REVISION;
if (handle != NULL)
{
// entête, version, révision
fwrite(gFileHeader, 1, 4, handle);
fwrite(&ver, 2, 1, handle);
fwrite(&rev, 2, 1, handle);
// taille de la map
fwrite(&mSize.x, 4, 1, handle);
fwrite(&mSize.y, 4, 1, handle);
// données de la map
for (int y = 0; y < mSize.y; ++y)
for (int x = 0; x < mSize.x; ++x)
{
CTile* t = getTile(CVec2(x, y));
fwrite(&t->mType, 1, 1, handle);
for (int side = 0; side < eWallSideMax; ++side)
fwrite(&t->mWalls[side].mType, 1, 1, handle);
}
fclose(handle);
}
}
Donc le format ressemble à ça:
D'abord on écrit une chaine de 4 caractères "DMMP" pour que ça soit facile de voir si ce fichier contient des données d'une map.
Puis deux valeurs 16 bits qui contiennent les numéros de version et de révision.
Comme on va probablement faire beaucoup de modifications à ce format dans le futur, c'est important de connaitre quelle version du format
ce fichier utilise, pour éviter de lire des données qui n'existent pas ou les interpréter d'une mauvaise façon.
Puis deux valeurs 32 bits qui donnent la taille de la map en nombre de cases.
Et finalement, pour chaque case, on sauve son type et le type des ses 4 murs, chacun sur 1 octet.
La fonction load() est très similaire, à part qu'on verifie les valeurs de l'entête et que l'on allloue de la mémoire pour lire les données de la map.
Dans "editor/data/maps" j'ai mis une map de test très simple qui a été sauvée par cette fonction.