Partie 27: Parchemins et textes sur les murs
Téléchargements
Textes multi-lignes
<text id="SCROLL00">INVOQUE LE_FUL ET TU_OBTIENDRAS_UNE TOUCHE_MAGIQUE.</text>
<text id="SCROLL01">LES VIEUX OS_AURONT_NOUVELLE VIE</text>
Maintenant il faut qu'on écrive une fonction pour découper ces textes:
std::vector<std::string> CInterface::getTextLines(std::string text)
{
std::vector<std::string> lines;
size_t start = 0;
size_t end = 0;
while (true)
{
end = text.find('_', start);
if (end != std::string::npos)
{
lines.push_back(text.substr(start, end - start));
start = end + 1;
}
else
{
lines.push_back(text.substr(start, end));
break;
}
}
return lines;
}
Ici on utilise les fonctions de std::string pour couper les lignes et on les stocke dans un vector de std::string.
//------------------------------------------------------------
int CInterface::getCharSpacing(EFonts font)
{
if (font == eFontStandard)
return 6;
else if (font == eFontScroll)
return 6;
else
return 8;
}
//------------------------------------------------------------
#define CHAR_WIDTH 8
void CInterface::drawText(QImage* image, CVec2 pos, EFonts font, const char* text, QColor color)
{
[...]
int spacing = getCharSpacing(font);
[...]
int i = 0;
while (text[i] != 0)
{
int c = codes[text[i] - 32];
CRect rect(CVec2(c * CHAR_WIDTH, 0),
CVec2((c + 1) * CHAR_WIDTH - 1, file.height() - 1));
CVec2 cPos(pos.x + i * spacing, pos.y);
graph2D.drawImageAtlas(image, cPos, file, rect, (font != eFontWalls ? color : QColor(0, 0, 0, 0)));
i++;
}
}
Maintenant, avant d'écrire notre fonction multi-lignes, regardons encore le résultat qu'on veut obtenir.
void CInterface::drawTextMultiLines(QImage* image, CVec2 pos, EFonts font, std::string text, QColor color, int height, int lineHeight)
{
std::vector<std::string> lines = getTextLines(text);
// centre le texte verticalement
if (height != 0)
pos.y += (height - lines.size() * lineHeight) / 2;
// dessine chaque ligne de texte
for (size_t i = 0; i < lines.size(); ++i)
{
CVec2 pos2 = pos;
pos2.x -= lines[i].size() * getCharSpacing(font) / 2;
drawText(image, pos2, font, lines[i].c_str(), color);
pos.y += lineHeight;
}
}
On verra que ce n'est pas la version finale de cette fonction, car on va devoir faire quelques modifications pour
Les paramètres des objets
<!-- PARCHEMIN -->
<item name="ITEM105">
[...]
<param type="string">Texte</param>
</item>
Les paramètres des objets fonctionnent exactement de la même façon que ceux des cases et des murs:
class CMap
{
[...]
std::vector<std::vector<CParamType>> mTilesParams; // liste des paramètres pour chaque type de case
std::vector<std::vector<CParamType>> mWallsParams; // liste des paramètres pour chaque type de mur
std::vector<std::vector<CParamType>> mObjectsParams; // liste des paramètres pour chaque type d'objet
[...]
};
class CObject
{
[...]
std::vector<CParam*> mParams;
[...]
};
Paramètres des objets dans l'éditeur
void CEditor::addStackParamsItems(CObjectStack* stack, int startId, QQuickItem* parent, std::vector<QQuickItem *> *list)
{
size_t size = 0;
if (stack != NULL)
{
for (size_t i = 0; i < stack->getSize(); ++i)
{
CObject& obj = stack->getObject(i);
int objType = obj.getType();
addButton(parent, "effacer", list, i);
addComboBox(parent, bridge.itemsList, list, startId + i, objType);
if (objType != 0)
{
std::vector<CParamType>& sourceList = map.mObjectsParams[objType - 1];
for (size_t j = 0; j < sourceList.size(); ++j)
{
CParam* param = obj.mParams[j];
if (sourceList[j].mType == eParamString)
{
CParamString* par = (CParamString*)param;
addLabel(parent, sourceList[j].mName + ":", list);
addTextField(parent, QString::fromStdString(par->mValue), list, (j + 1) * 0x10000 + i);
}
}
}
}
size = stack->getSize();
}
addButton(parent, "ajouter", list, size);
}
Vous pouvez voir qu'on utilise une petite astuce pour l'identifiant qu'on donne au champ de texte.
<index du paramètre + 1> * 0x10000 + <index de l'objet>
Ensuite, dans CBridge::setSelParamText() on peut retrouver le bon paramètre quand l'utilisateur change le texte
void CBridge::setSelParamText(qint32 id, QString value)
{
[...]
if (mTabIndex == eTabTiles)
{
[...]
}
else if (mTabIndex == eTabWalls)
{
CVec2 pos = editor.mSelectStart / TILE_SIZE;
EWallSide side = editor.getWallSideAbs(editor.mSelectStart);
if (id >= 0x10000)
{
CObjectStack* stack = map.findObjectsStack(pos, side);
int objNum = id & 0xFFFF;
int paramNum = (id / 0x10000) - 1;
CObject& obj = stack->getObject(objNum);
CParamType paramInfos = map.mObjectsParams[obj.getType() - 1][paramNum];
if (paramInfos.mType == eParamString)
{
obj.setStringParam(paramInfos.mName, value.toLocal8Bit().constData());
}
}
else
{
[...]
}
}
else if (mTabIndex == eTabObjects)
{
[même code pour les tas sur le sol]
}
}
Afficher les parchemins
void CInterface::drawScroll(QImage* image, CObject& object)
{
QImage scrollBg = fileCache.getImage("gfx/interface/Scroll.png");
graph2D.drawImage(image, CVec2(80, 85), scrollBg);
if (isPressingEye == true)
{
QImage eye = fileCache.getImage("gfx/interface/Eye.png");
graph2D.drawImage(image, CVec2(83, 90), eye);
}
else
{
QImage arrow = fileCache.getImage("gfx/interface/Arrow.png");
graph2D.drawImage(image, CVec2(83, 90), arrow);
}
std::string text = object.getStringParam("Texte");
drawTextMultiLines(image, CVec2(162, 91), eFontScroll, getText(text), SCROLL_TEXT_COLOR, 59, 7);
}
Vous devez avoir remarqué qu'il y a un test où on affiche soit un oeil ou une flèche.
void CInterface::drawBodyPart(QImage* image, CVec2 pos, int championNum, CCharacter::EBodyParts part, bool enableArea)
{
CCharacter* c = &game.characters[championNum];
int objType = c->bodyObjects[part].getType();
if (objType != 0)
{
// dessine l'objet dans ce slot
CObjects::CObjectInfo object = objects.mObjectInfos[objType - 1];
QImage objImage = fileCache.getImage(object.imageFile.toLocal8Bit().constData());
int imageNum = object.imageNum;
// objets ouverts dans la main
if (part == CCharacter::eBodyPartRightHand)
{
if (objType == OBJECT_TYPE_SCROLL)
imageNum = 30;
}
CRect rect = getItemRect(imageNum);
graph2D.drawImageAtlas(image, pos, objImage, rect);
}
[...]
}
Les textes des murs
<wall name="Texte">
<image>WallText.png</image>
<param type="string">Texte</param>
</wall>
Voici les textes des 2 murs du premier niveau:
<text id="WALL_TEXT00">HALL DES_CHAMPIONS</text>
<text id="WALL_TEXT01">L'AUTEL_VIM_DE LA_RENAISSANCE</text>
Comme pour les autres types de murs, celui là est affiché dans CGame::drawWall().
else if (wall->getType() == eWallText)
{
//------------------------------------------------------------------------------
// mur avec texte
std::string text = wall->getStringParam("Texte");
text = interface.getText(text);
if (tablePos == CVec2(2, 3) && side == eWallSideUp)
{
interface.drawTextMultiLines(image, CVec2(112, 74), CInterface::eFontWalls, text, QColor(), 0, 11);
}
}
Mais si vous lancez le code comme ça, voici ce que vous verrez:
else if (wall->getType() == eWallText)
{
[...]
if (tablePos == CVec2(2, 3) && side == eWallSideUp)
{
QImage patch = fileCache.getImage("gfx/3DView/TextPatch.png");
graph2D.drawImage(image, CVec2(111, 97), patch);
interface.drawTextMultiLines(image, CVec2(112, 74), CInterface::eFontWalls, text, QColor(), 0, 11);
}
}
void CInterface::drawTextMultiLines(QImage* image, CVec2 pos, EFonts font, std::string text, QColor color, int height, int lineHeight)
{
[...]
// dessine chaque ligne de texte
for (size_t i = 0; i < lines.size(); ++i)
{
[...]
drawText(image, pos2, font, lines[i].c_str(), color);
pos.y += lineHeight;
if (font == eFontWalls && i == 1)
pos.y += 3;
}
}
Et voila ce qu'on obtient:
Décor "Texte"
else if (wall->getType() == eWallText)
{
[...]
if (tablePos == CVec2(2, 3) && side == eWallSideUp)
{
[...]
}
else
{
std::vector<std::string> lines = interface.getTextLines(text);
walls.drawOrnate(image, tablePos, side, ORNATE_TEXT, lines.size());
}
}
Et il y a une autre petite astuce ici.
CRect CWalls::drawOrnate(QImage* image, CVec2 tablePos, EWallSide side, int ornate, int textLines)
{
if (ornate != 0)
{
[...]
if (gWallsInfos[tablePos.y][tablePos.x][tableSide].size.x != 0)
{
[...]
// calcule la position et le facteur d'échelle et dessine le décor dans une image temporaire
float scale = (float)ornateTabData->size.y / 111.0f;
QImage ornateImage = fileCache.getImage(ornateFileName.toLocal8Bit().constData());
if (ornate == ORNATE_TEXT)
{
CRect rect;
rect.tl = CVec2(0, 0);
rect.br.x = ornateImage.width() - 1;
if (side == eWallSideUp)
{
static int heights[] = {0, 14, 27, 41, 55};
rect.br.y = heights[textLines];
}
else
{
static int heights[] = {0, 10, 20, 31, 41};
rect.br.y = heights[textLines];
}
ornateImage = graph2D.cropImage(ornateImage, rect);
}
[...]
Ici on utilise une nouvelle fonction de graph2D qui coupe une image suivant un rectangle donné:
QImage CGraphics2D::cropImage(const QImage& srcImage, CRect rect)
{
QRect qrect(rect.tl.x, rect.tl.y, rect.br.x - rect.tl.x + 1, rect.br.y - rect.tl.y + 1);
return srcImage.copy(qrect);
}