Partie 28: Interface du jeu - Partie 4: Messages, sorts et armes
Téléchargements
Messages
				
		struct SMessage
		{
			std::string text;
			int champion;
			int time;
		};
		std::list<SMessage>   mMessages;
		int     messagePos;
				
				On stockera les structures des messages dans une liste.
Ajouter un message à la liste
		void CInterface::addMessage(std::string text, int champion)
		{
				
				D'abord on doit tester si la liste contient 4 lignes. Si c'est le cas on retire le plus vieux message:
				
			if (mMessages.size() == 4)
				mMessages.pop_back();
				
				Ensuite on remplit juste une nouvelle structure de message avec nos valeurs et on l'ajoute à la liste.
			SMessage    newMessage;
			newMessage.text = text;
			newMessage.champion = champion;
			newMessage.time = MSG_TIME;
			mMessages.push_front(newMessage);
				
				Enfin on remplit la position du dernier message, qui est celui qu'on vient d'ajouter.
			messagePos = MSG_START_POS;
		}
				
				MSG_START_POS est défini comme "200 * MSG_POS_FACTOR".
Ecrire les messages
		void CInterface::drawMessages(QImage* image)
		{
			std::list<SMessage>::iterator it;
			int pos = messagePos;
			for (it = mMessages.begin(); it != mMessages.end(); ++it)
			{
				QColor  color;
				if (it->champion < 4)
				{
					color = QColor(championsColors[it->champion][0],
					               championsColors[it->champion][1],
					               championsColors[it->champion][2]);
				}
				else
				{
					color = MAIN_INTERFACE_COLOR;
				}
				drawText(image, CVec2(0, pos / MSG_POS_FACTOR), eFontStandard, (it->text).c_str(), color);
				pos -= 7 * MSG_POS_FACTOR;
			}
		}
				
			
			
Mettre à jour les messages
		void CInterface::updateMessages()
		{
			// scrolling
			if (messagePos > MSG_DEST_POS)
				messagePos -= MSG_SCROLL_SPEED;
			if (messagePos < MSG_DEST_POS)
				messagePos = MSG_DEST_POS;
			// décrémente les temps
			std::list<SMessage>::iterator it;
			for (it = mMessages.begin(); it != mMessages.end();)
			{
				it->time--;
				if (it->time == 0)
					it = mMessages.erase(it);
				else
					++it;
			}
		}
				
			
			
Le message de résurrection
				
		<text id="MESSAGE00"># RESSUSCITE.</text>
				
				Voici le code pour remplacer le caractère "#":
				
		std::string CInterface::setChampionNameInString(int champion, std::string stringId)
		{
			std::string result = getText(stringId);
			size_t  pos = result.find('#');
			if (pos != std::string::npos)
				result.replace(pos, 1, game.characters[champion].firstName);
			return result;
		}
				
				Avant d'ajouter le texte à la liste, j'ai du écrire une autre fonction pour retrouver le dernier champion ajouté à
		int CInterface::findLastChampion()
		{
			for (int i = 3; i >= 0; --i)
				if (game.characters[i].portrait != -1)
					return i;
			return -1;
		}
				
				Maintenant on peut ajouter le message quand on ressuscite un personnage dans CInterface::update():
				
		// bouton ressusciter
		case eMouseArea_Resurrect:
		{
			game.lastMirrorClicked->setBoolParam("estVide", true);
			isResurrecting = false;
			mainState = eMainGame;
			int champ = findLastChampion();
			std::string message = setChampionNameInString(champ, "MESSAGE00");
			addMessage(message, champ);
		}
		break;
				
			
			
La zone des sorts
				
La zone de sélection du lanceur
				
		CCharacter* c = &game.characters[currentSpellCaster];
		for (int i = 0; i < currentSpellCaster; ++i)
		{
			if (isChampionEmpty(i) == false && isChampionDead(i) == false)
			{
				CRect   rect(CVec2(233 + 14 * i, 42),
				             CVec2(244 + 14 * i, 48));
				graph2D.rect(image, rect, MAIN_INTERFACE_COLOR, true);
				if (isSpellsGreyed() == false)
					mouse.addArea(eMouseArea_SpellCaster, rect, eCursor_Arrow, (void*)i);
			}
		}
				
				Puis le gros onglet du lanceur avec son nom.
				
		if (isChampionEmpty(currentSpellCaster) == false && isChampionDead(currentSpellCaster) == false)
		{
			CRect   rect(CVec2(233 + 14 * currentSpellCaster, 42),
			             CVec2(277 + 14 * currentSpellCaster, 49));
			graph2D.rect(image, rect, MAIN_INTERFACE_COLOR, true);
			drawText(image, rect.tl + CVec2(2, 2), eFontStandard, c->firstName.c_str(), BLACK);
		}
				
				Et finalement une boucle qui ressemble à la première pour les boutons à droite du lanceur.
				
		for (int i = currentSpellCaster + 1; i < 4; ++i)
		{
			if (isChampionEmpty(i) == false && isChampionDead(i) == false)
			{
				CRect   rect(CVec2(266 + 14 * i, 42),
				             CVec2(277 + 14 * i, 48));
				graph2D.rect(image, rect, MAIN_INTERFACE_COLOR, true);
				if (isSpellsGreyed() == false)
					mouse.addArea(eMouseArea_SpellCaster, rect, eCursor_Arrow, (void*)i);
			}
		}
				
				Dans CInterface::update(), on gère les zones souris qu'on a ajoutés en changeant simplement le lanceur courant.
		// change le jeteur de sort
		case eMouseArea_SpellCaster:
			currentSpellCaster = (int)clickedArea->param1;
		break;
				
			
			
Les boutons des symboles
				
		int currentPage = 0;
		if (isChampionEmpty(currentSpellCaster) == false && isChampionDead(currentSpellCaster) == false)
			currentPage = strlen(c->spell) % 4;
				
				Ensuite, on dessine chaque symbole comme on l'a fait dans la dernière partie qui traitait de l'interface.
		char word[2] = "a";
		for (int i = 0; i < 6; ++i)
		{
			CVec2 pos(239 + i * 14, 54);
			word[0] = 'a' + currentPage * 6 + i;
			drawText(image, pos, eFontStandard, word, MAIN_INTERFACE_COLOR);
			if (isChampionEmpty(currentSpellCaster) == false &&
				isChampionDead(currentSpellCaster) == false &&
				isSpellsGreyed() == false)
			{
				CRect   rect(CVec2(235 + 14 * i, 51),
				             CVec2(247 + 14 * i, 61));
				mouse.addArea(eMouseArea_SpellLetter, rect, eCursor_Arrow, (void*)((int)word[0]));
			}
		}
				
				Quand une de ces zones souris est pressée, on ajoute le symbole correspondant au sort.
		// ajoute un symbole au sort
		case eMouseArea_SpellLetter:
		{
			CCharacter* c = &game.characters[currentSpellCaster];
			if (strlen(c->spell) == 4)
				c->spell[0] = 0;
			int i = strlen(c->spell);
			c->spell[i] = (int)clickedArea->param1;
			c->spell[i + 1] = 0;
		}
		break;
				
			
			
Le bouton "effacer"
				
		// flèche "effacer"
		if (isChampionEmpty(currentSpellCaster) == false && isChampionDead(currentSpellCaster) == false)
		{
			CRect   rect(CVec2(305, 63), CVec2(318, 73));
			mouse.addArea(eMouseArea_SpellBack, rect, eCursor_Arrow);
		}
				
				Quand elle est cliquée, on efface la dernière lettre du sort si il n'est pas vide.
				
		// efface le dernier symbole du sort
		case eMouseArea_SpellBack:
		{
			CCharacter* c = &game.characters[currentSpellCaster];
			int i = strlen(c->spell);
			if (i != 0)
				c->spell[i - 1] = 0;
		}
		break;
				
			
			
Le bouton "lancer le sort"
				
		// sort courant
		if (isChampionEmpty(currentSpellCaster) == false && isChampionDead(currentSpellCaster) == false)
		{
			drawText(image, CVec2(237, 66), eFontStandard, c->spell, MAIN_INTERFACE_COLOR, 1);
			if (isSpellsGreyed() == false)
			{
				CRect   rect(CVec2(234, 63), CVec2(303, 73));
				mouse.addArea(eMouseArea_CastSpell, rect, eCursor_Arrow);
			}
		}
				
				Quand on clique sur ce bouton, on lance le sort.
		// jette un sort
		case eMouseArea_CastSpell:
		{
			CCharacter* c = &game.characters[currentSpellCaster];
			if (strlen(c->spell) != 0)
			{
				std::string message = setChampionNameInString(currentSpellCaster, "MESSAGE01");
				addMessage(message, 4);
				c->spell[0] = 0;
			}
		}
		break;
				
			
			
La zone des armes
				
Les boutons d'armes
		void    CInterface::drawWeapon(QImage* image, int num)
		{
			if (isWeaponEmpty(num) == false)
			{
				// dessine le rectangle de fond
				CRect   rect(CVec2(233 + 22 * num, 86),
				             CVec2(252 + 22 * num, 120));
				graph2D.rect(image, rect, MAIN_INTERFACE_COLOR, true);
				
				Maintenant pour dessiner les armes en noir, on utilise la même fonction que pour dessiner les textes dans une
				// dessine l'arme
				CVec2       pos = CVec2(235 + 22 * num, 96);
				int         weapon = getWeapon(num).getType();
				std::string imageFile;
				int         imageNum;
				if (weapon != 0)
				{
					CObjects::CObjectInfo  object = objects.mObjectInfos[weapon - 1];
					imageFile = object.imageFile.toLocal8Bit().constData();
					imageNum = object.imageNum;
				}
				else
				{
					// main vide
					imageFile = "gfx/interface/Items6.png";
					imageNum = 9;
				}
				QImage  objImage = fileCache.getImage(imageFile);
				CRect   objRect = getItemRect(imageNum);
				graph2D.drawImageAtlas(image, pos, objImage, objRect, BLACK);
				
				Enfin on dessine un tramage quand l'arme est grisée, ou on ajoute une zone souris quand elle ne l'est pas.
				
				// griser ou zone souris
				if (isWeaponGreyed(num) == true)
					graph2D.patternRectangle(image, rect);
				else
					mouse.addArea(eMouseArea_Weapon, rect, eCursor_Arrow, (void*)num);
			}
		}
				
				Notez que la fonction isWeaponGreyed() dépend de la variable de cooldown de l'arme.
		// clique sur une arme
		case eMouseArea_Weapon:
			currentWeapon = (int)clickedArea->param1;
			weaponsAreaState = eWeaponsAreaAttacks;
		break;
				
			
			
			
La liste d'attaques
				
		void    CInterface::drawAttacks(QImage* image, int num)
		{
			// dessine l'image de fond
			QImage  attacksBg = fileCache.getImage("gfx/interface/WeaponsActions.png");
			CVec2   pos(233, 77);
			graph2D.drawImage(image, pos, attacksBg);
				
				On efface les slots inutiles en dessinant un rectangle noir par dessus.
				
			// cache les slots inutiles
			int nbAttacks = (num % 3) + 1;
			if (nbAttacks != 3)
			{
				CRect   rect(CVec2(233, 98 + (nbAttacks - 1) * 12),
							 CVec2(319, 121));
				graph2D.rect(image, rect, BLACK, true);
			}
				
				On écrit le nom du champion en haut de la liste
			// écrit le nom du personnage
			CCharacter* c = &game.characters[num];
			drawText(image, CVec2(235, 79), eFontStandard, c->firstName.c_str(), BLACK);
				
				On écrit le nom des attaques. Remarquez qu'une d'entre elles peut être en surbrillance (on verra ça après).
			// écrit le nom des attaques
			for (int i = 0; i < nbAttacks; ++i)
			{
				static char attackName[16];
				sprintf(attackName, "ATTACK%02d", i);
				drawText(image, CVec2(241, 89 + i * 12), eFontStandard, getText(attackName).c_str(), MAIN_INTERFACE_COLOR);
				if (attackHighlightTime != 0 && currentAttack == i)
				{
					CRect   rect(CVec2(234, 86 + i * 12),
					             CVec2(318, 96 + i * 12));
					graph2D.Xor(image, rect, MAIN_INTERFACE_COLOR);
				}
			}
				
				Enfin on positionne les zones souris
				
			// zones souris
			if (isWeaponGreyed(num) == false && attackHighlightTime == 0)
			{
				for (int i = 0; i < nbAttacks; ++i)
				{
					CRect   rect(CVec2(234, 86 + i * 12),
					             CVec2(318, 96 + i * 12));
					mouse.addArea(eMouseArea_Attack, rect, eCursor_Arrow, (void*)i);
				}
				CRect   rect(CVec2(290, 77), CVec2(314, 83));
				mouse.addArea(eMouseArea_CloseAttack, rect, eCursor_Arrow);
			}
		}
				
				Quand on clique sur une de ces zones souris, on ne quitte pas la liste tout de suite, mais l'attaque cliquée
		// clique sur une attaque
		case eMouseArea_Attack:
			currentAttack = (int)clickedArea->param1;
			attackHighlightTime = ATTACK_HIGHLIGHT_TIME;
		break;
				
				
				
		void CInterface::updateWeapons()
		{
			// surbrillance de l'attaque
			if (attackHighlightTime > 0)
			{
				attackHighlightTime--;
				if (attackHighlightTime == 0)
				{
					weaponCoolDown[currentWeapon] = currentAttack * 60;
					if ((rand() % 2) == 0)
					{
						weaponsAreaState = eWeaponsAreaWeapons;
					}
					else
					{
						damages = rand() % 200 + 1;
						damagesDisplayTime = DAMAGES_DISPLAY_TIME;
						weaponsAreaState = eWeaponsAreaDamage;
					}
				}
			}
				
			
			
La vignette de dégats
				
		void CInterface::drawDamages(QImage *image)
		{
			QImage  damagesBg = fileCache.getImage("gfx/interface/DamageDone.png");
			static char damagesStr[8];
			sprintf(damagesStr, "%d", damages);
			int     length = strlen(damagesStr);
			float   scaleX = 0.44f + (length - 1) * 0.24f;
			CVec2   pos(258 - (length - 1) * 10, 81);
			graph2D.drawImageScaled(image, pos, damagesBg, scaleX, false, 0.82f);
			CVec2   textPos(274 - (length - 1) * 3, 97);
			drawText(image, textPos, eFontStandard, damagesStr, MAIN_INTERFACE_COLOR);
		}
				
				Sa temporisation est aussi mise à jour dans la fonction updateWeapons(). A la fin, on revient aux boutons d'armes.
		// timer des dégats
		if (damagesDisplayTime > 0)
		{
			damagesDisplayTime--;
			if (damagesDisplayTime == 0)
				weaponsAreaState = eWeaponsAreaWeapons;
		}
				
			
			
Le cooldown
				
		// cooldowns des armes
		for (int i = 0; i < 4; ++i)
			if (weaponCoolDown[i] > 0)
				weaponCoolDown[i]--;
				
				On doit seulement attendre que le timer finisse pour être capable de cliquer dessus à nouveau.