Part 45: Monsters Part 5: monsters' death
Downloads
Bug fix
void CSoundManager::pushTimerExpired()
{
[...]
// sort the sounds
std::vector<CSound*> sorted;
for (size_t i = 0; i < mSounds.size(); ++i)
sorted.push_back(&mSounds[i]);
for (int i = 0; i < (int)mSounds.size() - 1; ++i)
for (int j = i + 1; j < (int)mSounds.size(); ++j)
if (sorted[j]->getDistance() < sorted[i]->getDistance())
{
CSound* temp = sorted[i];
sorted[i] = sorted[j];
sorted[j] = temp;
}
// play the 4 nearest sounds (the others' samples are skipped)
int leftVolume, rightVolume;
for (size_t i = 0; i < mSounds.size(); ++i)
{
if (i < MIXER_COEF)
{
sorted[i]->getVolumes(&leftVolume, &rightVolume);
sorted[i]->getData(mBuffer, len, leftVolume, rightVolume);
}
else
{
sorted[i]->skipSamples(len);
}
}
Saving the monsters
class CSnapshot
{
public:
[...]
std::vector<CMonsterGroup2> monsterGroups;
[...]
};
//-----------------------------------------------------------------------------------------
CSnapshot& CSnapshot::operator=(const CSnapshot& rhs)
{
[...]
monsterGroups = rhs.monsterGroups;
[...]
}
//-----------------------------------------------------------------------------------------
void CGame::snapshotLevel()
{
[...]
s.monsterGroups = monsters.monsterGroups;
[...]
}
//-----------------------------------------------------------------------------------------
void CGame::loadLevel(int level, CVec2 pos, int side)
{
[...]
// search if the new level was snapshoted...
for (size_t i = 0; i < mVisitedLevels.size(); ++i)
{
CSnapshot* s = &mVisitedLevels[i];
if (s->level == level)
{
[...]
monsters.monsterGroups = s->monsterGroups;
return;
}
}
// ...else load the new level
[...]
}
When the snapshot is saved to the disk, we have to save the monsters datas too.
//---------------------------------------------------------------------------------------------
void CSnapshot::save(FILE* handle)
{
[...]
for (size_t i = 0; i < map.mMonsterGroups.size(); ++i)
monsterGroups[i].save(handle);
}
//---------------------------------------------------------------------------------------------
void CMonsterGroup2::save(FILE* handle)
{
uint8_t val8;
val8 = dir;
fwrite(&val8, 1, 1, handle);
val8 = state;
fwrite(&val8, 1, 1, handle);
fwrite(&startPos.x, sizeof(startPos.x), 1, handle);
fwrite(&startPos.y, sizeof(startPos.y), 1, handle);
for (int i = 0; i < 4; ++i)
mMonsters[i].save(handle);
}
//---------------------------------------------------------------------------------------------
void CMonster::save(FILE* handle)
{
uint8_t val8;
val8 = pos;
fwrite(&val8, 1, 1, handle);
val8 = dir;
fwrite(&val8, 1, 1, handle);
fwrite(&life, sizeof(life), 1, handle);
}
And obviously there are corresponding load functions.
Dropping objects
void CMonsterGroup2::hitMonster(CVec2 groupPos, int monsterNum, int damages)
{
[...]
// dead ?
if (mMonsters[monsterNum].life <= 0)
{
[...]
// if all monsters are dead, we remove the group
[...]
if (i == 4)
{
// drop objects on the ground
CObjectStack* stack = map.findObjectsStack(startPos, eWallSideMonster);
if (stack != NULL)
{
sound->play(groupPos, "sound/Wooden_Thud.wav");
for (size_t j = 0; j < stack->getSize(); ++j)
{
CObject obj = stack->getObject(j);
CVec2 objPos = groupPos * 2 + CVec2(RANDOM(2), RANDOM(2));
CObjectStack* newStack = map.addObjectsStack(objPos, eWallSideMax);
newStack->addObject(obj);
}
map.removeObjectsStack(startPos, eWallSideMonster);
}
// delete group
[...]
}
}
}
The screamers drop additional objects. Each of them can drop between 0 and 2 "Screamer Slice".
void CMonsters::init()
{
for (size_t i = 0; i < map.mMonsterGroups.size(); ++i)
{
[...]
group.startPos = mapGroup.mPos;
[...]
// add objects dropped by certain types of monsters
CObject obj;
if (mapGroup.getType() == MONSTER_TYPE_SCREAMER)
{
obj.setType(OBJECT_TYPE_SCREAMER_SLICE);
for (int j = 0; j < nbMonsters; ++j)
{
int nbObj = RANDOM(3);
for (int k = 0; k < nbObj; ++k)
{
CObjectStack* stack = map.addObjectsStack(group.startPos, eWallSideMonster);
stack->addObject(obj);
}
}
}
Smoke animation
void CGame::displayMainView(QImage* image)
{
[...]
// draw monsters and clouds
for (int row = 0; row < 3; row++)
{
monsters.draw(image, mapPos, tablePos, row);
explosions.drawCloud(image, mapPos, tablePos, row);
}
// draw explosions
explosions.drawExplosion(image, mapPos, tablePos);
[...]
I called these smokes "clouds", as there will be similar to poison clouds.
void CExplosions::drawCloud(QImage* image, CVec2 mapPos, CVec2 tablePos, int row)
{
for (size_t i = 0; i < mExploList.size(); ++i)
{
SExplosion& explo = mExploList[i];
if (explo.mapPos == mapPos && getRow(explo) == row)
{
QImage exploGfx;
float height = 0.0;
float scale = 0.0;
bool flip = false;
QColor color = TRANSPARENT;
switch (explo.type)
{
case eExplo_MonsterDie:
exploGfx = fileCache.getImage("gfx/3DView/explosions/Poison.png");
height = 0.636;
if (explo.timer.get() > explo.startTime * 2 / 3)
{
scale = 0.374;
}
else if (explo.timer.get() > explo.startTime / 3)
{
scale = 0.237;
flip = true;
}
else
{
scale = 0.173;
}
color = MONSTER_CLOUD_COLOR;
break;
default:
break;
}
if (scale == 0.0)
continue;
Then we will compute the position of the center of the sprite.
// bottom of the walls (ground level)
static const int aGround[] = {98, 106, 125, 152};
// top of the walls (ceiling level)
static const int aCeiling[] = {61, 58, 52, 42};
We will first get the coordinates of the coordinates of the monster.
// calculate the position and scale factor and draw the explosion in a temporary image
CVec2 pos = monsters.getMonsterPos2(mapPos, tablePos, explo.monsterPos);
Then we interpolate the coordinates to get the height of the smoke as if it was on the front or on the rear wall -
// height intepolation
float rearHeight = height * (float)aCeiling[tablePos.y] + (1.0 - height) * (float)aGround[tablePos.y];
float frontHeight = height * (float)aCeiling[tablePos.y + 1] + (1.0 - height) * (float)aGround[tablePos.y + 1];
Now we interpolate these 2 heights with the "depth" of the monster inside the tile to get the final height.
// depth interpolation
float distance = (float)(row + 1) * 0.25;
float meanHeight = distance * rearHeight + (1.0 - distance) * frontHeight;
// rounding the result
pos.y = (int)(meanHeight + 0.5);
Then we compute the scale of the image and we change its color if necessary.
// compute the scale based on the reference position (the nearest)
CVec2 pos0, pos1;
float tablePosY = tablePos.y * 2 + monsterPosDir[player.dir][explo.monsterPos - eMonsterPosNorthWest][1];
pos0 = CMonsters::getMonsterPos(WALL_TABLE_WIDTH, (WALL_TABLE_HEIGHT - 1) * 2 + 1);
pos1 = CMonsters::getMonsterPos(WALL_TABLE_WIDTH, tablePosY);
scale *= 2.68 * (float)(pos1.x - 112) / (float)(pos0.x - 112);
// set color
QImage tempImage;
QImage* srcImage;
if (color.alpha() == 0)
{
srcImage = &exploGfx;
}
else
{
tempImage = exploGfx;
graph2D.drawImageAtlas(&tempImage, CVec2(), exploGfx, CRect(CVec2(), CVec2(exploGfx.width(), exploGfx.height())), color);
srcImage = &tempImage;
}
The following of the function is very similar to the one we use to draw the monsters: scaling, darkening and
// scale the explosion
QImage scaledExplo(exploGfx.size() * scale, QImage::Format_ARGB32);
scaledExplo.fill(TRANSPARENT);
graph2D.drawImageScaled(&scaledExplo, CVec2(), *srcImage, scale);
// darken the explosion based on it's distance
float shadow = ((WALL_TABLE_HEIGHT - 1) * 2 - tablePosY - 1) * 0.13f;
graph2D.darken(&scaledExplo, shadow);
// draw the explosion (pos relative to its encter)
pos.x -= scaledExplo.width() / 2;
pos.y -= scaledExplo.height() / 2;
graph2D.drawImage(image, pos, scaledExplo, 0, flip, QRect(0, 33, MAINVIEW_WIDTH, MAINVIEW_HEIGHT));
}
}
}