Partie 40: Les monstres partie 2: Paramètres et affichage
Téléchargements
Paramètres
<!-- 01 Scorpion Géant -->
<monster name = "MONSTER01">
[...]
<param type="int">nbMonstres</param>
<param type="enum" values="Haut;Bas;Gauche;Droite">Dir</param>
<param type="stack"/>
</monster>
<!-- 02 Démon Baveux -->
<monster name = "MONSTER02">
[...]
<param type="int">nbMonstres</param>
<param type="enum" values="Haut;Bas;Gauche;Droite">Dir</param>
<param type="stack"/>
</monster>
"nbMonstres" est le nombre de monstres dans ce groupe.
enum EWallSide
{
eWallSideUp = 0,
eWallSideDown,
eWallSideLeft,
eWallSideRight,
eWallSideMax
};
#define eWallSideMonster (EWallSide)(eWallSideMax + 1)
Bien sur ces paramètres impliquent des changements dans les outils de l'éditeur, mais je n'expliquerai pas ça en
Charger et initialiser les monstres
void CMonsters::readMonstersDB()
{
QDomDocument doc;
QFile f("databases/monsters.xml");
f.open(QIODevice::ReadOnly);
doc.setContent(&f);
f.close();
QDomElement root = doc.documentElement();
QDomNode monsterNode = root.firstChild();
while(!monsterNode.isNull())
{
if (monsterNode.isElement())
{
QDomElement monster = monsterNode.toElement();
std::vector<CParamType> paramsTypesList;
if (monster.tagName() == "monster")
{
QDomElement monsterInfo = monster.firstChild().toElement();
SMonsterData monsterData;
while(!monsterInfo.isNull())
{
if (monsterInfo.tagName() == "img_front")
{
monsterData.frontImage = QString("gfx/3DView/monsters/") + monsterInfo.text();
}
else if (monsterInfo.tagName() == "img_side")
{
monsterData.sideImage = QString("gfx/3DView/monsters/") + monsterInfo.text();
}
else if (monsterInfo.tagName() == "img_back")
{
monsterData.backImage = QString("gfx/3DView/monsters/") + monsterInfo.text();
}
else if (monsterInfo.tagName() == "img_attack")
{
monsterData.attackImage = QString("gfx/3DView/monsters/") + monsterInfo.text();
}
else if (monsterInfo.tagName() == "param")
{
QString paramType = monsterInfo.attribute("type");
CParamType newParamType;
if (paramType == "int")
{
newParamType.mType = eParamInt;
newParamType.mName = monsterInfo.text();
paramsTypesList.push_back(newParamType);
}
else if (paramType == "enum")
{
newParamType.mType = eParamEnum;
newParamType.mName = monsterInfo.text();
newParamType.mValues = monsterInfo.attribute("values").split(";");
paramsTypesList.push_back(newParamType);
}
else if (paramType == "stack")
{
newParamType.mType = eParamStack;
paramsTypesList.push_back(newParamType);
}
}
monsterInfo = monsterInfo.nextSibling().toElement();
}
CMap::mMonstersParams.push_back(paramsTypesList);
monstersDatas.push_back(monsterData);
}
}
monsterNode = monsterNode.nextSibling();
}
}
Vous pouvez voir que les données des groupes sont stockées dans difféentes listes.
class CMonsters
{
public:
[...]
struct SMonsterData
{
QString frontImage;
QString sideImage;
QString backImage;
QString attackImage;
};
[...]
private:
[...]
std::vector<SMonsterData> monstersDatas;
};
Une fois qu'on a chargé la base de données, les groupes chargés dans le fichier map sont stockés dans la liste
class CMonsters
{
public:
[...]
struct SMonsterGroup
{
SMonster monsters[4];
EMonsterDir dir;
};
[...]
private:
[...]
std::vector<SMonsterGroup> monsterGroups;
};
Vous pouvez voir que le groupe peut contenir 4 monstres. Après avoir chargé la map, on appelle la fonction init()
class CMonsters
{
public:
enum EMonsterPos
{
eMonsterPosNone, // monstre vide
eMonsterPosNorthWest,
eMonsterPosNorthEast,
eMonsterPosSouthWest,
eMonsterPosSouthEast,
eMonsterPosCenter
};
enum EMonsterDir
{
eMonsterDirUp,
eMonsterDirDown,
eMonsterDirLeft,
eMonsterDirRight
};
struct SMonsterData
{
QString frontImage;
QString sideImage;
QString backImage;
QString attackImage;
};
struct SMonster
{
EMonsterPos pos;
EMonsterDir dir;
int life;
};
Comme vous pouvez le voir, chaque monstre a une position dans le groupe, sa propre direction et un compteur de vie.
void CMonsters::init()
{
for (size_t i = 0; i < map.mMonsterGroups.size(); ++i)
{
CMonsterGroup& mapGroup = map.mMonsterGroups[i];
SMonsterGroup group;
int nbMonsters = mapGroup.getIntParam("nbMonstres");
group.dir = (EMonsterDir)mapGroup.getEnumParam("Dir");
for (int j = 0; j < nbMonsters; j++)
group.monsters[j].dir = group.dir;
if (nbMonsters == 1)
{
group.monsters[0].pos = eMonsterPosCenter;
group.monsters[0].life = 3;
for (int j = 1; j < 4; j++)
{
group.monsters[j].pos = eMonsterPosNone;
group.monsters[j].life = 0;
}
}
else
{
// positions des monstres pour une direction donnée
static const EMonsterPos startingPos[4][4] =
{
{eMonsterPosNorthWest, eMonsterPosNorthEast, eMonsterPosSouthWest, eMonsterPosSouthEast}, // up
{eMonsterPosSouthEast, eMonsterPosSouthWest, eMonsterPosNorthEast, eMonsterPosNorthWest}, // down
{eMonsterPosNorthWest, eMonsterPosSouthWest, eMonsterPosNorthEast, eMonsterPosSouthEast}, // left
{eMonsterPosSouthEast, eMonsterPosNorthEast, eMonsterPosSouthWest, eMonsterPosNorthWest} // right
};
for (int j = 0; j < 4; j++)
{
if (j < nbMonsters)
{
group.monsters[j].pos = startingPos[group.dir][j];
group.monsters[j].life = 3;
}
else
{
group.monsters[j].pos = eMonsterPosNone;
group.monsters[j].life = 0;
}
}
}
monsterGroups.push_back(group);
}
}
Dessiner les monstres
void CMonsters::draw(QImage* image, CVec2 mapPos, CVec2 tablePos)
{
if (tablePos.y == WALL_TABLE_HEIGHT - 1)
return;
int groupIndex = map.findMonsterGroupIndex(mapPos);
if (groupIndex != -1)
{
CMonsterGroup& mapGroup = map.mMonsterGroups[groupIndex];
SMonsterGroup& group = monsterGroups[groupIndex];
// position des monstres en fonction de la position du joueur
static const float monsterPosDir[4][5][2] =
{
// NW NE SW SE C
{{0.0, 0.0}, {1.0, 0.0}, {0.0, 1.0}, {1.0, 1.0}, {0.5, 0.5}}, // haut
{{1.0, 0.0}, {1.0, 1.0}, {0.0, 0.0}, {0.0, 1.0}, {0.5, 0.5}}, // gauche
{{1.0, 1.0}, {0.0, 1.0}, {1.0, 0.0}, {0.0, 0.0}, {0.5, 0.5}}, // bas
{{0.0, 1.0}, {0.0, 0.0}, {1.0, 1.0}, {1.0, 0.0}, {0.5, 0.5}} // droite
};
// direction des monstres en fonction de la direction du joueur
static const EMonsterDir monsterDirDir[4][4] =
{
{eMonsterDirUp, eMonsterDirDown, eMonsterDirLeft, eMonsterDirRight}, // haut
{eMonsterDirRight, eMonsterDirLeft, eMonsterDirUp, eMonsterDirDown}, // gauche
{eMonsterDirDown, eMonsterDirUp, eMonsterDirRight, eMonsterDirLeft}, // bas
{eMonsterDirLeft, eMonsterDirRight, eMonsterDirDown, eMonsterDirUp} // droite
};
// on dessine la ligne arrière des monstres avant ceux de devant
for (int row = 0; row < 3; ++row)
{
Ensuite, on récupère la direction du monstre par rapport au joueur et on trouve son sprite.
for (int i = 0; i < 4; ++i)
{
SMonster& monster = group.monsters[i];
if (monster.pos != eMonsterPosNone)
{
// direction relative
EMonsterDir tableDir = monsterDirDir[player.dir][monster.dir];
// sprite
int type = mapGroup.getType();
QString spriteName;
bool flip = false;
if (tableDir == eMonsterDirDown)
{
spriteName = monstersDatas[type].frontImage;
}
else if (tableDir == eMonsterDirUp)
{
spriteName = monstersDatas[type].backImage;
if (spriteName.isEmpty() == true)
spriteName = monstersDatas[type].frontImage;
}
else
{
spriteName = monstersDatas[type].sideImage;
if (spriteName.isEmpty() == true)
spriteName = monstersDatas[type].frontImage;
else if (tableDir == eMonsterDirLeft)
flip = true;
}
Si le monstre est dans le rang qu'on veut dessiner, on calcule sa position à l'écran.
if (monsterPosDir[player.dir][monster.pos - eMonsterPosNorthWest][1] == (float)row / 2.0)
{
// position dans la table
float tablePosX = tablePos.x * 2 + monsterPosDir[player.dir][monster.pos - eMonsterPosNorthWest][0];
float tablePosY = tablePos.y * 2 + monsterPosDir[player.dir][monster.pos - eMonsterPosNorthWest][1];
// position à l'écran
QImage sprite = fileCache.getImage(spriteName.toStdString());
CVec2 pos = getMonsterPos(tablePosX, tablePosY);
On le redimensionne et on l'assombrit comme la plupart des éléments du jeu
// calcule la taille par rapport à la position de référence (la plus proche)
CVec2 pos0, pos1;
float scale;
pos0 = getMonsterPos(WALL_TABLE_WIDTH, (WALL_TABLE_HEIGHT - 1) * 2 + 1);
pos1 = getMonsterPos(WALL_TABLE_WIDTH, tablePosY);
scale = 2.37 * (float)(pos1.x - 112) / (float)(pos0.x - 112);
// redimensionne le monstre dans une image temporaire
QImage scaledMonster(sprite.size() * scale, QImage::Format_ARGB32);
scaledMonster.fill(TRANSPARENT);
graph2D.drawImageScaled(&scaledMonster, CVec2(), sprite, scale);
// assombrit le monstre en fonction de la distance
float shadow = ((WALL_TABLE_HEIGHT - 1) * 2 - tablePosY - 1) * 0.13f;
graph2D.darken(&scaledMonster, shadow);
Et enfin on l'affiche en se basant sur la position de ses pieds.
// dessine le monstre (position relative à ses pieds)
pos.x -= scaledMonster.width() / 2;
pos.y -= scaledMonster.height() - 9 * scale;
graph2D.drawImage(image, pos, scaledMonster, 0, flip, QRect(0, 33, MAINVIEW_WIDTH, MAINVIEW_HEIGHT));
}
}
}
}
}
}