Partie 25: Objets - Partie 5: Poids et informations

Téléchargements

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

Le champion sélectionné

Un des 4 champions est différent des autres.
A chaque instant, il y a toujours un champion "sélectionné" que vous pouvez reconnaitre par la couleur de son nom.


C'est ce champion qui transporte l'objet que vous avez comme curseur.
Quand vous tenez un objet avec votre souris, son poids est ajouté à la charge de ce champion.
Vous pouvez changer le champion sélectionné en cliquant sur le nom d'un autre.

J'ai remplacé tous les noms de personnages qu'on affichait par une nouvelle fonction:

		void CInterface::drawChampionName(QImage* image, int num, CVec2 pos, bool isFullName)
		{
			CCharacter* c = &game.characters[num];
			std::string name = c->firstName;

			if (isFullName == true)
				name += " " + c->lastName;

			QColor  color = (num == selectedChampion ? HIGHLIGHT_TEXT_COLOR : DEFAULT_TEXT_COLOR);
			drawText(image, pos, eFontStandard, name.c_str(), color);
		}
				
Elle affiche le prénom ou le nom complet avec la bonne couleur.
La plupart des couleurs utilisées dans l'interface sont maintenant définies dans "interface.h":

		#define MAIN_INTERFACE_COLOR    QColor(0, 204, 204)
		#define DEFAULT_TEXT_COLOR      QColor(173, 173, 173)
		#define HIGHLIGHT_TEXT_COLOR    QColor(255, 170, 0)
		#define INVENTORY_BG_COLOR      QColor(68, 68, 68)
		#define INVENTORY_SLOT_COLOR    QColor(102, 102, 102)
		#define OVERLOAD1_COLOR         QColor(255, 255, 0)
		#define OVERLOAD2_COLOR         QColor(255, 0, 0)
				
Pour changer le champion sélectionné, on doit définir des zone souris autour des noms des champions dans
CInterface::drawChampion():

		CRect nameRect(basePos, basePos + CVec2(42, 6));
		mouse.addArea(eMouseArea_SelectChamp, nameRect, eCursor_Arrow, (void*)num);
				
Et quand on la teste, on doit simplement changer la valeur de selectedChampion.

		void   CInterface::update(SMouseArea* clickedArea)
		{
			[...]

			if (clickedArea != NULL)
			{
				if (mouse.mButtonPressing == true)
				{
					switch (clickedArea->type)
					{
						[...]

						// clique sur le nom du champion
						case eMouseArea_SelectChamp:
							selectedChampion = (int)clickedArea->param1;
						break;
				

Les poids

J'ai ajouté un poids à chaque objet dans "items.xml".
Remarquez que les poids sont multipliés par 10 parce que c'est plus simple de travailler avec des entiers.

		<?xml version="1.0" encoding="utf-8"?>
		<items>
			<!-- ======================== Armes ======================== -->
			<!-- OEIL DU TEMPS -->
			<item name="ITEM001">
				[...]
				<weight>1</weight>
			</item>

			<!-- ROND D'ECLAIRS -->
			<item name="ITEM002">
				[...]
				<weight>1</weight>
			</item>

			<!-- TORCHE -->
			<item name="ITEM003">
				[...]
				<weight>11</weight>
			</item>

			[...]
		</items>
				
J'ai aussi écrit une fonction pour calculer la charge d'un personnage.

		void CCharacter::updateLoad()
		{
			int type;
			stats[eStatLoad].value = 0;

			for (int i = 0; i < eBodyPartCount; ++i)
			{
				type = bodyObjects[i].getType();

				if (type != 0)
					stats[eStatLoad].value += objects.mObjectInfos[type - 1].weight;
			}

			if (&game.characters[interface.selectedChampion] == this)
			{
				type = mouse.mObjectInHand.getType();

				if (type != 0)
					stats[eStatLoad].value += objects.mObjectInfos[type - 1].weight;
			}
		}
				
On fait simplement la somme des poids de tous les objets portés par le personnage.
De plus, si c'est le personnage sélectionné, on ajoute le poids de l'objet sur la souris.

On n'a pas besoin d'appeler cette fonction 60 fois par seconde, on l'appelle seulement pendant l'initialisation du
personnage, quand on ramasse un objet ou quand on en pose un sur le sol...

		void CGame::update(SMouseArea* clickedArea)
		{
			[...]

			if (clickedArea != NULL)
			{
				if (mouse.mButtonPressing == true)
				{
					if (clickedArea->type == eMouseAreaG_Champion)
					{
						[...]
					}
					else if (clickedArea->type == eMouseAreaG_DoorButton)
					{
						[...]
					}
					else if (clickedArea->type == eMouseAreaG_PickObject)
					{
						[...]
						characters[interface.selectedChampion].updateLoad();
					}
					else if (clickedArea->type == eMouseAreaG_DropObject)
					{
						[...]
						characters[interface.selectedChampion].updateLoad();
					}
				}
			}
		}
				
...et quand on prend ou qu'on pose un objet dans l'inventaire d'un personnage.

		void   CInterface::update(SMouseArea* clickedArea)
		{
			[...]

			if (clickedArea != NULL)
			{
				if (mouse.mButtonPressing == true)
				{
					switch (clickedArea->type)
					{
						[...]

						// slot d'objet dans l'inventaire
						case eMouseArea_InvObject:
						{
							[...]

							// met à jour la charge du personnage
							c->updateLoad();
							c = &game.characters[selectedChampion];
							c->updateLoad();
						}
						break;
				

Surcharge

Il y a 3 niveaux de surcharge différents dans ce jeu.
Quand la charge est basse, elle est affichée en gris.


Quand la charge est supérieure à 62.5% du maximum, elle est affichée en jaune.


Quand la charge est supérieure au maximum, elle est affichée en rouge.


Alors j'ai écrit une simple fonction pour renvoyer le niveau de surcharge d'un personnage:

		int CCharacter::overload()
		{
			int value = stats[eStatLoad].value;
			int max = stats[eStatLoad].maxValue;

			if (value > max)
				return 2;
			else if (value > (625 * max) / 1000)
				return 1;
			else
				return 0;
		}
				
Et je l'ai utilisée pour choisir la couleur quand on affiche la charge:

		void    CInterface::drawInventory(QImage* image)
		{
			[...]

			// affiche la charge
			CCharacter* c = &game.characters[currentChampion];
			QColor  loadColor;
			if (c->overload() == 0)
				loadColor = DEFAULT_TEXT_COLOR;
			else if (c->overload() == 1)
				loadColor = OVERLOAD1_COLOR;
			else
				loadColor = OVERLOAD2_COLOR;

			drawText(image, CVec2(104, 161), eFontStandard, getText("STATS09").c_str(), loadColor);
			static char temp[256];
			CCharacter::SStat stat = getStat(currentChampion, CCharacter::eStatLoad);
			sprintf(temp, "%5.1f/%3d KG", (float)stat.value / 10.0f, stat.maxValue / 10);

			drawText(image, CVec2(148, 161), eFontStandard, temp, loadColor);

			[...]
		}
				
Mais c'était la partie facile. En dehors de la couleur du texte, la surcharge a aussi un effet sur divers
paramètres. Principalement sur le niveau d'énergie (comme on le verra dans une future partie, c'est assez buggué
dans le jeu d'origine), mais aussi sur la vitesse de déplacement.

Quand la surcharge est au premier niveau pour tous les champions, la vitesse sera la même que celle qu'on a vue
depuis le début de ce projet. Mais quand un personnage sera plus chargé, le déplacement sera ralenti.
Ca veut dire qu'il y aura un timer pour vous empêcher de vous déplacer immédiatement quand vous appuyez sur une
touche ou que vous cliquez sur une flèche.
Donc, dans CInterface::updateArrows(), au lieu de se déplacer directement, on va appeler une fonction
setDelayedArrow():

		if (isClickingOnArrow(clickedArea, eMouseArea_ArrowForward) == true || keyboard.keys[CKeyboard::eForward] == true)
		{
			arrowHighlightTime = ARROWS_HIGHLIGHT_TIME;
			currentArrow = eArrowForward;
			setDelayedArrow(eArrowForward);
		}
		else if (isClickingOnArrow(clickedArea, eMouseArea_ArrowBackward) == true || keyboard.keys[CKeyboard::eBackward] == true)
		{
			arrowHighlightTime = ARROWS_HIGHLIGHT_TIME;
			currentArrow = eArrowBackward;
			setDelayedArrow(eArrowBackward);
		}
		else if (isClickingOnArrow(clickedArea, eMouseArea_ArrowLeft) == true || keyboard.keys[CKeyboard::eLeft] == true)
		{
			arrowHighlightTime = ARROWS_HIGHLIGHT_TIME;
			currentArrow = eArrowLeft;
			setDelayedArrow(eArrowLeft);
		}
		else if (isClickingOnArrow(clickedArea, eMouseArea_ArrowRight) == true || keyboard.keys[CKeyboard::eRight] == true)
		{
			arrowHighlightTime = ARROWS_HIGHLIGHT_TIME;
			currentArrow = eArrowRight;
			setDelayedArrow(eArrowRight);
		}
		else if (isClickingOnArrow(clickedArea, eMouseArea_ArrowTurnLeft) == true || keyboard.keys[CKeyboard::eTurnLeft] == true)
		{
			arrowHighlightTime = ARROWS_HIGHLIGHT_TIME;
			currentArrow = eArrowTurnLeft;
			player.turnLeft();
		}
		else if (isClickingOnArrow(clickedArea, eMouseArea_ArrowTurnRight) == true || keyboard.keys[CKeyboard::eTurnRight] == true)
		{
			arrowHighlightTime = ARROWS_HIGHLIGHT_TIME;
			currentArrow = eArrowTurnRight;
			player.turnRight();
		}
				
Cette fonction initialise seulement 2 variables. delayedArrow est l'identifiant de la flèche qu'on a cliqué et
delayedArrowTime est un compteur comme celui qu'on a utlisé pour temporiser la mise en valeur des flèches.

		void    CInterface::setDelayedArrow(EArrows arrow)
		{
			delayedArrow = arrow;

			int maxOverload = 0;
			for (int i = 0; i < 4; ++i)
			{
				int overload = game.characters[i].overload();

				if (overload > maxOverload)
					maxOverload = overload;
			}

			if (maxOverload == 0)
				delayedArrowTime = OVERLOAD0_DELAY;
			else if (maxOverload == 1)
				delayedArrowTime = OVERLOAD1_DELAY;
			else
				delayedArrowTime = OVERLOAD2_DELAY;
		}
				
Donc, dans updateArrow(), si delayedArrowTime n'est pas égal à 0, on ne testera pas le clavier ou les flèches, et à
la fin de cette fonction, on va décrémenter delayedArrowTime et vraiment exécuter le mouvement si on arrive à 0.

		void   CInterface::updateArrows(SMouseArea* clickedArea)
		{
			if (delayedArrowTime == 0)
			{
				if (isClickingOnArrow...
				[...]
			}

			// mouvements retardés
			if (delayedArrowTime != 0)
			{
				delayedArrowTime--;

				if (delayedArrowTime == 0)
				{
					switch(delayedArrow)
					{
						case eArrowForward:
							player.moveForward();
							break;

						case eArrowBackward:
							player.moveBackward();
							break;

						case eArrowLeft:
							player.moveLeft();
							break;

						case eArrowRight:
							player.moveRight();
							break;

						default:
							break;
					}
				}
			}
		}
				
Dans la map, vous verrez que j'ai ajouté un paquet d'objets pour que vous puissiez tester la surcharge.

Les infos des objets

J'ai aussi codé la fenêtre d'infos qui apparait quand vous mettez un objet sur l'oeil.


Le code est simplement une question d'affichage d'images et de textes, alors je ne vais pas l'expliquer en détail.
Remarquez seulement que certains objets ont 2 lignes de texte (la gourde et les objets comestibles) mais la
plupart ont seulement leur poids d'affiché.

J'ai mis cette fenêtre d'infos et celle pour les statistiques du personnage dans une nouvelle fonction appelée
drawInfos:

		void    CInterface::drawInfos(QImage* image)
		{
			// dessine l'image des infos
			QImage  infoBg = fileCache.getImage("gfx/interface/EmptyInfo.png");
			graph2D.drawImage(image, CVec2(80, 85), infoBg);

			int objType = mouse.mObjectInHand.getType();

			if (objType == 0)
			{
				// infos du champion
				[...]
			}
			else
			{
				// infos d'un objet
				QImage  eye = fileCache.getImage("gfx/interface/Eye.png");
				graph2D.drawImage(image, CVec2(83, 90), eye);

				QImage  circle = fileCache.getImage("gfx/interface/Circle.png");
				graph2D.drawImage(image, CVec2(106, 87), circle);

				CObjects::CObjectInfo&  infos = objects.mObjectInfos[objType - 1];
				QImage  objSheet = fileCache.getImage(infos.imageFile.toLocal8Bit().constData());
				CRect   objRect = interface.getItemRect(infos.imageNum);
				graph2D.drawImageAtlas(image, CVec2(111, 92), objSheet, objRect);

				// nom de l'objet
				drawText(image, CVec2(134, 98), eFontStandard, infos.name.c_str(), DEFAULT_TEXT_COLOR);

				int y = 116;

				if (objType == 144)
				{
					// gourde pleine
					drawText(image, CVec2(108, y), eFontStandard, getText("OBJ_INFO00").c_str(), DEFAULT_TEXT_COLOR);
					y += 7;
				}
				else if (objType == 145)
				{
					// gourde vide
					drawText(image, CVec2(108, y), eFontStandard, getText("OBJ_INFO03").c_str(), DEFAULT_TEXT_COLOR);
					y += 7;
				}
				else if (infos.positions.contains("c"))
				{
					// comestible
					drawText(image, CVec2(108, y), eFontStandard, getText("OBJ_INFO04").c_str(), DEFAULT_TEXT_COLOR);
					y += 7;
				}

				// poids
				static char temp[256];
				sprintf(temp, "%s %.1f KG.", getText("OBJ_INFO05").c_str(), (float)infos.weight / 10.0f);
				drawText(image, CVec2(108, y), eFontStandard, temp, DEFAULT_TEXT_COLOR);
			}
		}