Partie 34: Portes réparées et boutons

Téléchargements

Code source
Exécutable de l'éditeur de niveaux (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.

Réparer les portes

Dans la dernière partie on a eu des problèmes avec les décors des portes.
Certains n'avaient pas la même couleur que la porte, et des parties des décors transparents étaient manquantes à
cause du redimensionnement.
Pour réparer ça, on va dessiner les portes d'une nouvelle façon.
D'abord, on ne va pas utiliser toutes les images des portes, seulement la plus proche.


Ensuite, on va dessiner le décor dessus, en rendant éventuellement les pixels transparents si nécessaire.



Finalement, on va assombrir et redimensionner l'image complète.




C'est ce que l'on fait dans la fonction drawDoor().

		void    CDoors::drawDoor(QImage* image, CVec2 tablePos, CTile* tile)
		{
			SDoorElem&  cell = tabDoor[tablePos.y][tablePos.x];
			int doorNum = cell.file[0] - '0';

			if (doorNum >= 0)
			{
				//----------------------------------------------------------------------------
				// récupère l'image de la porte
				int doorType = tile->getDoorParam("Type");
				std::string fileName = std::string("gfx/3DView/doors/") + doorsDatas[doorType].files[2].toUtf8().constData();
				QImage  doorImage = fileCache.getImage(fileName);

				//----------------------------------------------------------------------------
				// récupère l'image du décor
				int ornateType = tile->getDoorOrnateParam("Decor");
				std::string ornateName = doorOrnatesDatas[ornateType].file.toUtf8().constData();

				QImage  ornateImage;
				CVec2   ornatePos;

				if (ornateName.empty() == false)
				{
					ornateImage = fileCache.getImage(std::string("gfx/3DView/door_ornates/") + ornateName);

					ornatePos = doorOrnatesDatas[ornateType].pos;
					graph2D.drawImage(&doorImage, ornatePos, ornateImage);

					// décors avec transparence
					if (ornateType == DOOR_ORNATE_ARCHED || ornateType == DOOR_ORNATE_ARCHED + 1)
					{
						QSize   qSize = ornateImage.size();

						for (int y = 0; y < qSize.height(); ++y)
							for (int x = 0; x < qSize.width(); ++x)
							{
								QRgb pixel = ornateImage.pixel(x, y);

								if (qRed(pixel) == 204 &&
									qGreen(pixel) == 136 &&
									qBlue(pixel) == 102)
								{
									CVec2   destPos = ornatePos + CVec2(x, y);
									doorImage.setPixel(destPos.x, destPos.y, 0);
								}
							}
					}
				}

				//----------------------------------------------------------------------------
				// assombrit
				static const float shadowLevels[] = {0.0f, 0.2f, 0.4f};
				float shadow = shadowLevels[WALL_TABLE_HEIGHT - 2 - tablePos.y];
				graph2D.darken(&doorImage, shadow);

				//----------------------------------------------------------------------------
				// dessin
				static const int doorScalesX[] = {96, 64, 44};
				static const int doorScalesY[] = {88, 61, 38};
				float scaleX = (float)doorScalesX[WALL_TABLE_HEIGHT - 2 - tablePos.y] / 96.0;
				float scaleY = (float)doorScalesY[WALL_TABLE_HEIGHT - 2 - tablePos.y] / 88.0;
				bool isHorizontal = tile->getBoolParam("estHorizontale");
				int doorPos = tile->getIntParam("pos") / DOOR_POS_FACTOR;

				if (isHorizontal == false)
				{
					//----------------------------------------------------------------------------
					// porte verticale
					doorPos *= scaleY;

					CVec2 pos(cell.x, cell.y + doorPos);
					QRect clip(cell.cx1, cell.cy1,
							   cell.cx2 - cell.cx1 + 1, cell.cy2 - cell.cy1 + 1);
					clipToMainView(&clip);

					if (clip.width() > 0 && clip.height() > 0)
						graph2D.drawImageScaled(image, pos, doorImage, scaleX, false, scaleY, clip);
				}
				else
				{
					//----------------------------------------------------------------------------
					// porte horizontale
					doorPos *= scaleX;

					CVec2 pos(cell.x + doorPos, cell.y);
					int halfwidth = (cell.cx2 - cell.cx1 + 1) / 2;

					QRect clip1(cell.cx1, cell.cy1,
							   halfwidth + doorPos, cell.cy2 - cell.cy1 + 1);
					clipToMainView(&clip1);

					if (clip1.width() > 0 && clip1.height() > 0)
						graph2D.drawImageScaled(image, pos, doorImage, scaleX, false, scaleY, clip1);

					pos.x = cell.x - doorPos;
					QRect clip2(cell.cx1 + halfwidth - doorPos, cell.cy1,
							   halfwidth + doorPos, cell.cy2 - cell.cy1 + 1);
					clipToMainView(&clip2);

					if (clip2.width() > 0 && clip2.height() > 0)
						graph2D.drawImageScaled(image, pos, doorImage, scaleX, false, scaleY, clip2);
				}
			}
		}
				

L'ombre des objets

J'ai ajouté une ombre sous l'objet qu'on tient dans la main.
Comme dans le jeu d'origine, j'ai simplement dessiné une copie noire de l'objet 2 pixels à droite et en bas du
curseur.


Les boutons

Les boutons ont 2 états (on et off) donc ils sont associés à 2 décors.

		<ornate name="Levier Off">
			<image_front>Lever_Off_front.png</image_front>
			<image_side>Lever_Off_side.png</image_side>
			<pos_front x="105" y="69"/>
			<pos_side x="47" y="72"/>
		</ornate>

		<ornate name="Levier On">
			<image_front>Lever_On_front.png</image_front>
			<image_side>Lever_On_side.png</image_side>
			<pos_front x="105" y="69"/>
			<pos_side x="47" y="72"/>
		</ornate>
				
Dans l'éditeur ils auront 2 paramètres de scripts et un booléen pour mémoriser leur état.

		<wall name="Bouton">
			<image>Switch.png</image>
			<param type="enum" values="Levier;Bouton vert;Bouton blau;Gros bouton">Type</param>
			<param type="script">siOn</param>
			<param type="script">siOff</param>
			<param type="bool">estOn</param>
		</wall>
				
Dans cette partie, j'ai écrit les scripts pour la plupart des boutons et quelques plaques de pression.
Vous verrez qu'on n'aura pas seulement à ouvrir des portes comme dans la dernière partie.
Il va y avoir pas mal de nouvelles commandes dans les scripts.

L'alcôve cachée

Le premier bouton que vous rencontrez derrière la porte numéro 1 est spécial.


Une fois que vous le pressez, il disparait et il est remplacé par une alcôve avec un cimeterre à l'intérieur.


Alors il nous faut 3 commandes pour ce script:

		Target Wall 6 3 down
		SetType 5
		SetEnum Type 1
		AddObject 10
				
SetType change le type de la target d'un mur de bouton à un mur d'alcôve.
SetEnum change le type de cette alcôve en une alcôve carrée.
Et AddObject ajoute le cimeterre au tas de la target courante (c'est-à-dire le tas associé à l'alcôve).
Leurs codes sont assez simples. Au passage, j'ai du changer la fonction executeTarget() pour garder la trace de la
position et du coté de la target, et pas seulement un pointeur dessus.

		//--------------------------------------------------------------------------
		void CScripts::executeSetType(std::vector<std::string>& words)
		{
			int type = stoi(words[1]);

			if (mTargetType == eTargetType_Tile)
			{
			}
			else
			{
				CWall*  target = (CWall*)mTarget;
				target->setType(type);
			}
		}

		//--------------------------------------------------------------------------
		void CScripts::executeSetEnum(std::vector<std::string>& words)
		{
			QString param = QString::fromStdString(words[1]);
			int type = stoi(words[2]);

			if (mTargetType == eTargetType_Tile)
			{
			}
			else
			{
				CWall*  target = (CWall*)mTarget;
				target->setEnumParam(param, type);
			}
		}

		//--------------------------------------------------------------------------
		void CScripts::executeAddObject(std::vector<std::string>& words)
		{
			int type = stoi(words[1]);
			CObjectStack*   stack = map.addObjectsStack(mTargetPos, mTargetSide);
			CObject newObj;
			newObj.setType(type);
			stack->addObject(newObj);
		}
				

Les 2 boutons suivants


Le 2ième bouton qu'on rencontre ouvre seulement la porte 5. Alors il sera associé à 2 scripts: "L02_OpenDoor05.txt"

		Target Tile 5 9
		SetBool estOuverte true
				
et "L02_CloseDoor05.txt"

		Target Tile 5 9
		SetBool estOuverte false
				
Le bouton suivant fermera un trou dans le sol. Ca sera pour une prochaine partie.

Le puzzle de la porte 19


Le bouton suivant apparaît à coté de la porte 19, et il l'ouvre.
Mais il fait partie d'un petit puzzle car la plaque de pression à coté de lui referme la porte.
Il n'y a rien de spécial ici. Le bouton est associé à un script "L02_OpenDoor19.txt", et la plaque est associée
à un "L02_CloseDoor19.txt".
Remarquez que le script du bouton est appelé quand il est mis sur "on" ou sur "off".


Le puzzle de la porte 20

Une fois que vous avez traversé la porte 19, vous vous trouvez en face de la porte 20. Il y a 2 leviers au bout de
2 couloirs ici.


L'astuce est que ces leviers doivent tous les 2 être sur "on" pour ouvrir la porte.
Alors dans chaque script de ces boutons, j'ai ajouté une commande "If" pour tester l'état de l'autre bouton.

		Target Tile 18 20
		If Wall 22 19 right isOn
		SetBool estOuverte true
				
Si la condition n'est pas remplie, la ligne suivant le "If" est sautée.
Pour effectuer ça, j'ai seulement du ajouter le test d'un booléen dans executeLine():

		void CScripts::executeLine(std::vector<std::string>& words)
		{
			if (lastIf == true)
			{
				if (words[0] == "Target")
					executeTarget(words);
				else if (words[0] == "SetBool")
					executeSetBool(words);
				else if (words[0] == "SwitchBool")
					executeSwitchBool(words);
				else if (words[0] == "SetType")
					executeSetType(words);
				else if (words[0] == "SetEnum")
					executeSetEnum(words);
				else if (words[0] == "AddObject")
					executeAddObject(words);
				else if (words[0] == "If")
					executeIf(words);
				else if (words[0] == "SetString")
					executeSetString(words);
			}
			else
			{
				lastIf = true;
			}
		}
				

La porte 24 et la pièce cachée


La porte 24 est ouverte par un levier dans la petite pièce à gauche.
Derrière cette porte, il y a un autre levier qui fait disparaître certains murs de la pièce à cote.
Pour réaliser ça il faut juste qu'on change le type de ces murs.

		Target Wall 14 28 down
		SetType 0
		Target Wall 13 29 right
		SetType 0
				

Le texte que disparait

Comme j'ajoutais de nouvelles commandes de script, j'ai aussi fait le puzzle du texte qui disparait.


Après être passé par la porte 16, vous rencontrez une plaque de pression et vous voyez un texte sur un mur plus
loin.


Le truc, c'est que si cous marchez sur la plaque, dès que vous la quittez, le texte disparait.
Vous devez laisser un objet sur la plaque pour être capable de lire ce texte.

Pour cacher le texte, on a seulement à changer le type du mur.

		Target Wall 22 8 left
		SetType 1
				
Mais pour le faire réapparaître, on doit à la fois rechanger le type en mur de texte, mais aussi remettre le
paramètre "Texte".

		Target Wall 22 8 left
		SetType 6
		SetString Texte WALL_TEXT03
				
La commande SetString est aussi simple que celle de SetBool:

		void CScripts::executeSetString(std::vector<std::string>& words)
		{
			QString param = QString::fromStdString(words[1]);

			if (mTargetType == eTargetType_Tile)
			{
			}
			else
			{
				((CWall*)mTarget)->setStringParam(param, words[2]);
			}
		}