Part 46: Monsters Part 6: wandering
Downloads
Monsters parameters
<!-- 01 Giant Scorpion -->
<monster name = "MONSTER01">
[...]
<movement_time>80</movement_time>
<move_sound>Move_Screamer.wav</move_sound>
[...]
</monster>
"move_sound" is the sound of the steps that will be played every time the group moves.
void CMonsterGroup2::initMoveTimer(int type)
{
CMonsters::SMonsterData& data = monsters.monstersDatas[type];
int t = MAX(10, RANDOM(40) + data.movementTime - 10);
moveTimer.set(t, true);
}
This timer is set when the group is initialized by calling an enterWanderState() function.
void CMonsterGroup2::enterWanderState(int type)
{
state = eMonsterState_Wandering;
initMoveTimer(type);
}
Now, in the wander state, everything appears only when the timer fires - even the transition to "fight" to avoid
void CMonsterGroup2::wander(CVec2 mapPos, uint8_t type)
{
if (moveTimer.update() == true)
{
initMoveTimer(type);
// transition to fight state ?
CVec2 dist = player.pos - mapPos;
bool isNearPlayer = (dist.x == 0 && ABS(dist.y) == 1) || (dist.y == 0 && ABS(dist.x) == 1);
if (isNearPlayer == true)
{
[...]
}
else
{
// wander
[...]
}
}
}
Moving
// list the possible directions
EWallSide possibleDirs[4];
int nbPossibleDirs = 0;
for (int dir = eWallSideUp; dir < eWallSideMax; ++dir)
if (canMove(mapPos, (EWallSide)dir) == true)
{
possibleDirs[nbPossibleDirs++] = (EWallSide)dir;
}
The canMove() function tells if we can walk on a given tile.
CVec2 CMonsterGroup2::getNewPos(CVec2 pos, EWallSide side)
{
CVec2 newPos = pos;
switch (side)
{
case eWallSideUp:
newPos.y--;
break;
case eWallSideDown:
newPos.y++;
break;
case eWallSideLeft:
newPos.x--;
break;
case eWallSideRight:
newPos.x++;
break;
default:
break;
}
return newPos;
}
//---------------------------------------------------------------------------------------------
bool CMonsterGroup2::canMove(CVec2 pos, EWallSide side)
{
CVec2 newPos = getNewPos(pos, side);
CTile* tile = map.getTile(pos);
CTile* destTile = map.getTile(newPos);
// hit a wall
if (tile->mWalls[side].getType() != 0)
return false;
// out of the map
if (destTile == NULL)
return false;
switch (destTile->getType())
{
case eTileDoor:
if (doors.isOpened(destTile) == false)
return false;
break;
case eTileStairs:
case eTileTeleporter:
return false;
case eTilePit:
if (destTile->getBoolParam("isClosed") == false)
return false;
break;
default:
break;
};
// monster group
if (map.findMonsterGroupIndex(newPos) != -1)
return false;
// player
if (newPos == player.pos)
return false;
return true;
}
Back in our main routine, now that we have the directions list, we choose one randomly, move the group and play the
if (nbPossibleDirs > 0)
{
// chose a random direction and move
EWallSide chosenDir = possibleDirs[RANDOM(nbPossibleDirs)];
CMonsterGroup* group = map.findMonsterGroup(mapPos);
group->mPos = getNewPos(mapPos, chosenDir);
if (data.moveSound.isEmpty() == false)
sound->play(group->mPos, data.moveSound.toLocal8Bit().constData());
}
And we rotate each monster in a random direction - even in the case that we did not move.
// rotate monsters randomly
for (int i = 0; i < 4; ++i)
mMonsters[i].dir = (EMonsterDir)RANDOM(4);
Last words