Partie 10: Listes de cases et de murs dans l'éditeur

Téléchargements

Code source
Exécutable de l'éditeur de niveaux (Windows 32bits)
Exécutable du jeu - exactement le même que la partie 7 (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.

Bugs corrigés

La dernière fois j'ai oublié de d'écrire la taille d'une sélection rectangulaire dnas la boite d'informations de l'éditeur. Maintenant c'est corrigé.
J'ai déplacé la plupart de ces informations dans la fonction toolSelect() d'"editor.cpp" pour éviter des comportements bizarres.

Vous pouvez maintenant copier ou couper un seul mur pour le coller plus tard. Je n'expliquerai pas ça en détail parce que ce n'est pas la partie la plus
intéressante de l'éditeur. Mais pour ceux qui sont intéressés, j'ai ajouté un mType et un mSide dans la classe clipboard.
La plupart du clipboard et des outils copier/couper/coller ont été réécrite pour prende en compte ce type.

J'ai aussi fait quelques changements au dessin du rectangle de sélection. Maintenant il est dessiné après le curseur pour qu'on puisse voir la différence
quand on colle une seule case ou un seul mur.

Les listes de cases et de murs

Les onglets "cases" et "murs" ne sont plus vides. Maintenant ils contiennent une liste des différents types que vous pouvez sélectionner quand vous utilisez
l'outil "dessin".


Ce sont de simples "ListViews" QML qui utilisent les même chaines de caractères que nous avons utilisées pour les ComboBoxes la dernière fois.
Regardons à quoi ça ressemble pour les cases dans "TilesTab.qml":

		ScrollView
		{
			x: 6
			y: 6
			width: parent.width - 12
			height: parent.height - 12

			ListView
			{
				model:tilesList
				currentIndex: bridge.tileType

				delegate: Item {
					width: parent.width - 12
					height: 25

					Text {
						x: 6
						width: parent.width - 12
						anchors.verticalCenter: parent.verticalCenter
						text: modelData
					}

					MouseArea {
						anchors.fill: parent
						onClicked: bridge.tileType = index
					}
				}

				highlight: Rectangle { color: "#70c0ff" }
			}
		}
				
D'abord je mets toute la liste dans une ScrollView. Comme elle va grandir à l'avenir, elle va peut-être devenir trop grande pour tenir dans
la tabView.
Ensuite, on trouve la ListView elle-même. Elle utilise la liste des noms de cases que nous avons définie dans la dernière partie comme "model",
ce sont les données qui vont être utilisées pour chaque ligne de la liste.
La ligne actuellement sélectionnée est liée à la variable bridge.tileType, qui est une variable partagée entre les parties QML et C++.
Le "delegate" définit la représentation graphique de chaque ligne. C'est seulement un "Item" invisible qui contient un texte centré.
Par défaut, les listviews QML sont seulement controlées avec le clavier. Donc on doit ajouter un "MouseArea" dans le delegate pour capturer
les clics de souris dans cette ligne.
Dans la dernière partie, on appelait une fonction de "bridge.cpp" quand la valeur d'une ComboBox changeait, car on devait exécuter du code
dans la partie C++. Ici, on n'a pas vraiment besoin de faire quelque chose chaque fois qu'on sélectionne un type de case. Alors on met seulement
à jour notre variable bridge.tileType qui va automatiquement mettre à jour le "currentIndex" de la listView (c'est la force du binding en QML).
Finalement le "highlight" définit ce qu'on veut dessiner dans le fond de la ligne actuellement sélectionnée (celle qui est définie par currentIndex).
Ici on dessine juste un rectangle bleu de la même taille que l'Item invisible du delegate.

Ensuite, dans l'outil de dessin, on aura juste à récupérer la valeur de "bridge.tileType" du coté C++ pour dessiner le bon type de case.

Les bases de données

Il y aura beaucoup de types de cases et de murs à l'avenir, qui contiendront probablement d'autres informations que leur simple nom.
Et ça serait une mauvaise idée de stocker toutes ces données dans des tableaux à l'intérieur du code.
Alors on va les stocker dans un fichier que sera lu au lancement de l'éditeur.
Ca sera aussi plus flexible si on veut utiliser l'éditeur pour un autre jeu.

J'ai choisi de les stocker en xml comme c'est un format commun, facile à modifier avec un éditeur de textes, et parce que Qt a déjà des fonctions
pour le lire.
Vous trouverez ces fichiers dans "editor/data/databases". regardons à quoi ressemble "tiles.xml":

		<?xml version="1.0" encoding="ISO-8859-1"?>
		<tiles>
			<tile name="Vide">
			</tile>

			<tile name="Sol">
				<image>Tile.png</image>
			</tile>
		</tiles>
				
Comme vous le voyez, il est assez simple. On définit 2 "tile"s appelées "Vide" et "Sol". Et pour la tile "Sol", on définit l'image qui sera utilisée
pour l'afficher dans l'éditeur.

Ces fichiers sont lus par les fonctions readTilesDB() et readWallsDB() à la fin de "editor.cpp".
J'ai utilisé des fonctions obsolètes pour les écrire, mais elles fonctionnent encore (au moins avec Qt 5.6) et elles sont plus faciles à comprendre que
leurs remplacements.

		void    CEditor::readTilesDB()
		{
			QDomDocument doc;

			QFile f("databases/tiles.xml");
			f.open(QIODevice::ReadOnly);
			doc.setContent(&f);
			f.close();

			QDomElement root = doc.documentElement();
			QDomElement tile = root.firstChild().toElement();

			while(!tile.isNull())
			{
				if (tile.tagName() == "tile")
				{
					CTileData   newTile;

					newTile.name = tile.attribute("name");

					QDomElement tileInfo = tile.firstChild().toElement();

					while(!tileInfo.isNull())
					{
						if (tileInfo.tagName() == "image")
							newTile.image = QString("tiles/") + tileInfo.text();

						tileInfo = tileInfo.nextSibling().toElement();
					}
					tilesDatas.push_back(newTile);
				}
				tile = tile.nextSibling().toElement();
			}
		}
				
readWallsDB() est pratiquement la même fonction.

Enfin, j'ai simplement changé la fonction displayMap() pour utiliser les noms de fichiers venant de ces xml.
Et pendant que j'y étais, j'ai aussi utilisé le cache de fichiers qu'on a déjà utilisé dans le jeu.