Partie 41: Monstres partie 3: Comportements et attaque
Téléchargements
Comportements de groupe
Implémentation des états
//---------------------------------------------------------------------------------------------
class CMonster
{
public:
[...]
EMonsterPos pos;
EMonsterDir dir;
int life;
[...]
};
//---------------------------------------------------------------------------------------------
class CMonsterGroup2
{
public:
[...]
CMonster monsters[4];
EMonsterDir dir;
[...]
};
Notez que j'ai renommé SMonsterGroup en CMonsterGroup2 pour éviter la confusion avec la classe CMonsterGroup
enum EMonsterState
{
eMonsterState_Wandering,
eMonsterState_Fight
};
class CMonsterGroup2
{
public:
[...]
EMonsterState state;
};
Pour changer la valeur de cette variable state, on va avoir besoin d'une fonction update() qui sera appelée à
void CMonsterGroup2::update(CVec2 mapPos, uint8_t type)
{
[...]
// gère le comportement du groupe pour chaque état
switch (state)
{
case eMonsterState_Wandering:
wander(mapPos, type);
break;
case eMonsterState_Fight:
fight(mapPos, type);
break;
}
}
Au niveau du groupe ces fonctions ne font pas grand chose à part gérer les transitions entre états en fonction de
//---------------------------------------------------------------------------------------------
void CMonsterGroup2::wander(CVec2 mapPos, uint8_t type)
{
// transition en état de combat ?
CVec2 dist = player.pos - mapPos;
bool isNearPlayer = (dist.x == 0 && ABS(dist.y) == 1) || (dist.y == 0 && ABS(dist.x) == 1);
if (isNearPlayer == true)
{
state = eMonsterState_Fight;
[...]
}
}
//---------------------------------------------------------------------------------------------
void CMonsterGroup2::fight(CVec2 mapPos, uint8_t type)
{
// transition en mode ballade ?
CVec2 dist = player.pos - mapPos;
bool isNearPlayer = (dist.x == 0 && ABS(dist.y) == 1) || (dist.y == 0 && ABS(dist.x) == 1);
if (isNearPlayer == false)
{
state = eMonsterState_Wandering;
}
}
Dans la fonction update de notre groupe on va aussi appeler une fonction update() pour chaque monstre à dans le
void CMonsterGroup2::update(CVec2 mapPos, uint8_t type)
{
// met à jour tous les monstres du groupe
for (int i = 0; i < 4; ++i)
if (monsters[i].pos != eMonsterPosNone)
monsters[i].update(*this, mapPos, type);
[...]
}
Comportements des monstres
void CMonster::update(CMonsterGroup2& group, CVec2 mapPos, uint8_t type)
{
displayAttackTimer.update();
switch (group.state)
{
case eMonsterState_Wandering:
wander();
break;
case eMonsterState_Fight:
fight(mapPos, type);
break;
}
}
On a dit qu'on ne ferait rien dans l'état balade, donc la fonction wander() sera vide.
Paramètres d'attaque
<monsters>
[...]
<!-- 01 Scorpion Géant -->
<monster name = "MONSTER01">
[...]
<time_between_atk>200</time_between_atk>
<atk_display_time>40</atk_display_time>
<atk_sound>Attack_Scorpion.wav</atk_sound>
[...]
</monster>
class CMonster
{
public:
[...]
CTimer displayAttackTimer;
CTimer nextAttackTimer;
};
Dans le jeu d'origine le temps entre 2 attaques était calculé avec une formule qui ressemblait à ça:
void CMonster::initNextAttack(uint8_t type)
{
int minTime = monsters.monstersDatas[type].timeBetweenAttack;
int nextTime = minTime + (rand() % 40) - 10;
if (minTime > 150)
nextTime += (rand() % 80) - 20;
nextAttackTimer.set(nextTime, true);
}
Cette fonction initialise un de nos timers alors on l'appellera au moment où on entre dans l'état "fight" du groupe.
void CMonsterGroup2::wander(CVec2 mapPos, uint8_t type)
{
// transition en état de combat ?
[...]
if (isNearPlayer == true)
{
state = eMonsterState_Fight;
// direction vers le joueur
EMonsterDir dir;
if (dist.x > 0)
dir = eMonsterDirRight;
else if (dist.x < 0)
dir = eMonsterDirLeft;
else if (dist.y > 0)
dir = eMonsterDirDown;
else
dir = eMonsterDirUp;
// initialise chaque monstre du groupe
for (int i = 0; i < 4; ++i)
if (monsters[i].pos != eMonsterPosNone)
{
monsters[i].dir = dir;
monsters[i].initNextAttack(type);
}
}
}
Ensuite, dans la fonction fight du monstre, on update ce timer.
void CMonster::fight(CVec2 mapPos, uint8_t type)
{
if (nextAttackTimer.update() == true)
{
QString soundName = monsters.monstersDatas[type].attackSound;
if (soundName.isEmpty() == false)
sound->play(mapPos, soundName.toLocal8Bit().constData());
int attackTime = monsters.monstersDatas[type].attackDisplayTime;
displayAttackTimer.set(attackTime, true);
initNextAttack(type);
}
}
Afficher l'attaque
// sprite
[...]
if (tableDir == eMonsterDirDown)
{
// de face
if (monster.displayAttackTimer.isRunning())
{
// attaque
spriteName = monstersDatas[type].attackImage;
if (spriteName.isEmpty() == true)
spriteName = monstersDatas[type].frontImage;
}
else
{
// normal
spriteName = monstersDatas[type].frontImage;
}
}
[...]