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.