Partie 36: Les trous

Téléchargements

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

Paramètres


Les trous sont très simples. Ils vont utiliser ces paramètres:

		<tile name="Trou">
			<image>Pit.png</image>
			<param type="int">Niveau</param>
			<param type="int">X</param>
			<param type="int">Y</param>
			<param type="bool">estFerme</param>
		</tile>
				
Niveau, X and Y définissent la position où on atterrit après être tombé dans le trou.
Et estFerme dit si il est ouvert ou fermé.

Graphismes

Les graphismes sont similaires aux décors de sol. On a des images pour les trous en face et à gauche.
Ceux de droite sont simplement les images de gauche retournées.


La fonction de dessin est très simple aussi. C'est assez similaire aux décors, à part que les graphismes et leurs
coordonnées sont codés en dur dans une table.

		struct SPitData
		{
			char        file[32];
			int16_t     x, y;
		};

		static const SPitData   gPits[WALL_TABLE_HEIGHT][(WALL_TABLE_WIDTH + 1) / 2] =
		{
			{{"Pit03.png", 0, 96}, {"Pit13.png",  6,  98}, {"Pit23.png", 80,  98}}, // Row 3
			{{"", 0, 0},           {"Pit12.png", -1, 109}, {"Pit22.png", 66, 110}}, // Row 2
			{{"", 0, 0},           {"Pit11.png", -3, 127}, {"Pit21.png", 43, 127}}, // Row 1
			{{"", 0, 0},           {"Pit10.png",  0, 159}, {"Pit20.png", 27, 160}}  // Row 0
		};

		void CTiles::drawPit(QImage* image, CVec2 tablePos)
		{
			const SPitData*  data;

			// récupère les données de la table
			bool    flip = (tablePos.x >= (WALL_TABLE_WIDTH + 1) / 2);

			if (flip == false)
				data = &gPits[tablePos.y][tablePos.x];
			else
				data = &gPits[tablePos.y][WALL_TABLE_WIDTH - tablePos.x - 1];

			// affichage
			if (data->file[0] != 0)
			{
				std::string fileName = std::string("gfx/3DView/pits/") + std::string(data->file);
				QImage  pitImage = fileCache.getImage(fileName);
				CVec2   pos = CVec2(data->x, data->y);

				if (flip == true)
					pos.x = MAINVIEW_WIDTH - pos.x - pitImage.width();

				QRect   clip(0, 33, MAINVIEW_WIDTH, MAINVIEW_WIDTH);
				graph2D.drawImage(image, pos, pitImage, 0, flip, clip);
			}
		}
				
Il n'y a pas de graphisme pour les trous fermés, même si dans les graphismes d'origine, il y avait des images de
contours qui n'ont pas été utilisées dans le jeu.

Il y a des graphismes pour les trous au plafond que vous pouvez voir dans le niveau en-dessous après être tombé,
mais je n'ai pas eu le temps de les inclure dans cette partie.

Tomber

Comme pour les escaliers et les téléporteurs, quand on marche sur la case d'un trou ouvert, on positionne un flag
qui sera géré dans la fonction mainLoop():

		if (destTile->getType() == eTilePit && destTile->getBoolParam("estFerme") == false)
		{
			sound->play(pos, "sound/Scream.wav");
			shouldFall = true;
			interface.setMainState(CInterface::eMainFall);
		}
				
La seule chose spéciale c'est que dans mainLoop() on attend que le son soit fini avant d'appeler la fonction pour
tomber.

		if (player.shouldFall && sound->isFinished(player.pos, "sound/Scream.wav") == true)
			player.fall();
				
Cette fonction fall() est une version simplifiée de la fonction teleport().

		void CPlayer::fall()
		{
			CTile*  tile = map.getTile(pos);
			int     level = tile->getIntParam("Niveau");
			CVec2   newPos = CVec2(tile->getIntParam("X"), tile->getIntParam("Y"));

			game.loadLevel(level, newPos, dir);
			shouldFall = false;
			interface.setMainState(CInterface::eMainGame);
		}
				

Poser des objets dans les trous

Poser des objets dans les trous est aussi une version simplifiée de la pose dans un téléporteur.
Quand on pose un objet sur une des ces cases, on appelle une fonction qui déplace tous les tas d'objets sur cette
case:

		void CGame::fallStacks(CVec2 mapPos)
		{
			CTile*  tile = map.getTile(mapPos);
			std::vector<CObjectStack>   copyStacks;

			// copie les tas
			for (int y = 0; y < 2; ++y)
				for (int x = 0; x < 2; ++x)
				{
					CVec2 objPos = mapPos * 2 + CVec2(x, y);
					CObjectStack*    stack = map.findObjectsStack(objPos, eWallSideMax);

					if (stack != NULL)
					{
						CObjectStack    copy = *stack;
						copy.mPos = CVec2(x, y);
						copyStacks.push_back(copy);
						map.removeObjectsStack(objPos, eWallSideMax);
					}
				}

			int     lastLevel = currentLevel;
			CVec2   lastPos = player.pos;
			uint8_t lastDir = player.dir;

			int     level = tile->getIntParam("Niveau");
			CVec2   newPos = CVec2(tile->getIntParam("X"), tile->getIntParam("Y"));

			// charge le niveau de destination
			loadLevel(level, CVec2(), 0);

			// mets les tas à la nouvelle position
			for (size_t i = 0; i < copyStacks.size(); ++i)
			{
				CVec2 objPos = newPos * 2 + copyStacks[i].mPos;
				CObjectStack*   stack = map.addObjectsStack(objPos, eWallSideMax);

				for (size_t j = 0; j < copyStacks[i].getSize(); ++j)
					stack->addObject(copyStacks[i].getObject(j));
			}

			// recharge le dernier niveau
			loadLevel(lastLevel, lastPos, lastDir);
		}
				

Le niveau 3 et les scripts

Pour tester les trous, j'ai du créer un niveau 3 minimal.
Vous pouvez le trouver dans le répertoire "data/maps" des sources.
Il y a seulement quelques cases et des téléporteurs pour vous ramener au niveau 2.
Mais au moins le jeu ne crashe plus quand on prend les escaliers à la fin du niveau 2.

J'ai aussi écrit quelques scripts pour ouvrir/fermer les trous en fonction des puzzles.
Ils sont aussi simples que ceux pour ouvrir/fermer les portes parce qu'on a seulement besoin de changer une valeur
booléenne.

Note: en traduisant cette partie, je me suis rendu compte que j'avais oublié quelque chose.
Quand on pose un objet sur un trou fermé, puis qu'on l'ouvre avec un script, les objets ne tombent pas.
Ca fera surement l'objet d'une future partie, avec les trous dans les plafonds.
Il faudra de toute façon faire la même chose pour les téléporteur, car certains puzzles en auront besoin.