Partie 29: Escaliers et mémorisation des maps
Téléchargements
Escaliers dans l'éditeur
<tile name="Escaliers">
<image>Stairs.png</image>
<param type="int">Niveau</param>
<param type="int">X</param>
<param type="int">Y</param>
<param type="enum" values="Haut;Gauche;Bas;Droite">Cote</param>
</tile>
Et le mur nous donnera l'orientation de l'image. J'ai choisi d'utiliser le mur du "fond" de l'escalier (celui qui
Les escaliers dans le jeu
struct SStairsData
{
char file[32];
uint16_t x, y;
};
// table pour les escaliers descendant, en face
static const SStairsData gDownFacingStairs[WALL_TABLE_HEIGHT][WALL_TABLE_WIDTH] =
{
{{"", 0, 0}, {"Down_13_F.png", 8, 63}, {"Down_23_F.png", 75, 58}, {"Down_13_F.png", 141, 63}, {"", 0, 0}}, // Row 3
{{"", 0, 0}, {"Down_12_F.png", 0, 57}, {"Down_22_F.png", 63, 57}, {"Down_12_F.png", 163, 57}, {"", 0, 0}}, // Row 2
{{"", 0, 0}, {"Down_11_F.png", 0, 51}, {"Down_21_F.png", 35, 50}, {"Down_11_F.png", 192, 51}, {"", 0, 0}}, // Row 1
{{"", 0, 0}, {"", 0, 0}, {"", 0, 0}, {"", 0, 0}, {"", 0, 0}} // Row 0
};
// table pour les escaliers montant, en face
static const SStairsData gUpFacingStairs[WALL_TABLE_HEIGHT][WALL_TABLE_WIDTH] =
{
[...]
};
// table pour les escaliers descendant, de coté
static const SStairsData gDownSideStairs[WALL_TABLE_HEIGHT][WALL_TABLE_WIDTH] =
{
{{"", 0, 0}, {"", 0, 0}, {"", 0, 0}, {"", 0, 0}, {"", 0, 0}}, // Row 3
{{"", 0, 0}, {"Side_12_F.png", 60, 89}, {"", 0, 0}, {"Side_12_F.png", 156, 89}, {"", 0, 0}}, // Row 2
{{"", 0, 0}, {"Down_Side_11_F.png", 32, 95}, {"", 0, 0}, {"Down_Side_11_F.png", 172, 95}, {"", 0, 0}}, // Row 1
{{"", 0, 0}, {"Side_10_F.png", 0, 106}, {"", 0, 0}, {"Side_10_F.png", 208, 106}, {"", 0, 0}} // Row 0
};
// table pour les escaliers montant, de coté
static const SStairsData gUpSideStairs[WALL_TABLE_HEIGHT][WALL_TABLE_WIDTH] =
{
[...]
};
void CTiles::drawStairs(QImage* image, CTile* tile, CVec2 tablePos)
{
const SStairsData* data;
// trouve le coté des escaliers
EWallSide side;
int i;
for (i = 0; i < 4; ++i)
if (tile->mWalls[i].getType() == eWallStairs)
break;
side = (EWallSide)i;
bool isDown = (tile->getIntParam("Niveau") > game.currentLevel);
if (side != player.getWallSide(eWallSideDown))
{
// récupère les données de la table
if (side == player.getWallSide(eWallSideUp))
{
// escaliers en face
if (isDown)
data = &gDownFacingStairs[tablePos.y][tablePos.x];
else
data = &gUpFacingStairs[tablePos.y][tablePos.x];
}
else
{
// escaliers de coté
if (isDown)
data = &gDownSideStairs[tablePos.y][tablePos.x];
else
data = &gUpSideStairs[tablePos.y][tablePos.x];
}
// affichage
if (data->file[0] != 0)
{
std::string fileName = std::string("gfx/3DView/stairs/") + std::string(data->file);
CVec2 pos = CVec2(data->x, data->y);
bool flip = (tablePos.x > 2);
QImage stairsImage = fileCache.getImage(fileName);
graph2D.drawImage(image, pos, stairsImage, 0, flip);
}
}
else if (tablePos == CVec2(2, 3))
{
// cas spécial si on est sur la case de l'escalier
if (isDown)
{
QImage stairsImage = fileCache.getImage("gfx/3DView/stairs/Down_20_L.png");
graph2D.drawImage(image, CVec2(0, 109), stairsImage);
graph2D.drawImage(image, CVec2(194, 109), stairsImage, 0, true);
}
else
{
QImage stairsImage = fileCache.getImage("gfx/3DView/stairs/Up_20_L.png");
graph2D.drawImage(image, CVec2(0, 91), stairsImage);
graph2D.drawImage(image, CVec2(194, 91), stairsImage, 0, true);
}
}
}
Prendre les escaliers
void CPlayer::takeStairs()
{
CTile* tile = map.getTile(pos);
int level = tile->getIntParam("Niveau");
CVec2 newPos = CVec2(tile->getIntParam("X"), tile->getIntParam("Y"));
int side = tile->getEnumParam("Cote");
game.loadLevel(level, newPos, side);
}
La fonction CGame::loadLevel() charge évidemment le niveau et positionne le joueur dans la nouvelle map.
//-----------------------------------------------------------------------------------------
void CPlayer::turnLeft()
{
shouldTakeStairs = false;
[...]
CTile* tile = map.getTile(pos);
if (tile->getType() == eTileStairs)
shouldTakeStairs = true;
}
//-----------------------------------------------------------------------------------------
void CPlayer::turnRight()
{
shouldTakeStairs = false;
[...]
CTile* tile = map.getTile(pos);
if (tile->getType() == eTileStairs)
shouldTakeStairs = true;
}
//-----------------------------------------------------------------------------------------
void CPlayer::moveLeft()
{
shouldTakeStairs = false;
[...]
}
//-----------------------------------------------------------------------------------------
void CPlayer::moveRight()
{
shouldTakeStairs = false;
[...]
}
//-----------------------------------------------------------------------------------------
void CPlayer::moveForward()
{
shouldTakeStairs = false;
[...]
}
//-----------------------------------------------------------------------------------------
void CPlayer::moveBackward()
{
shouldTakeStairs = false;
if (interface.isInInventory() == false)
{
CTile* tile = map.getTile(pos);
if (tile->getType() == eTileStairs)
{
shouldTakeStairs = true;
}
else
{
[...]
}
}
}
//-----------------------------------------------------------------------------------------
void CPlayer::moveIfPossible(EWallSide side)
{
[...]
if (canMove == true)
{
[...]
if (destTile->getType() == eTileStairs)
shouldTakeStairs = true;
}
}
Et à la fin de la boucle de jeu, on teste ce flag pour appeler notre fonction:
QImage& CGame::mainLoop()
{
[...]
if (player.shouldTakeStairs)
player.takeStairs();
return image;
}
Et on n'oublie pas de remettre ce flag à false dans takeStairs().
Mémorisation de la map
class CSnapshot
{
public:
CSnapshot();
CSnapshot(const CSnapshot& rhs);
virtual ~CSnapshot();
CSnapshot& operator=(const CSnapshot& rhs);
void copyPressPlates(bool* src);
int level;
CMap map;
bool* pressPlateStates;
private:
void freePressPlates();
};
class CGame
{
[...]
std::vector<CSnapshot> mVisitedLevels;
};
Les données qu'on va stocker pour chaque map sont:
CMap::CMap(const CMap& rhs)
{
mTiles = NULL;
*this = rhs;
}
Donc j'ai du écrire des constructeurs par copie pour quelques classes, et en même temps j'en ai profité pour écrire
void CGame::snapshotLevel()
{
if (currentLevel != 0)
{
// Si le niveau est déjà mémorisé, on l'efface
std::vector<CSnapshot>::iterator it;
for (it = mVisitedLevels.begin(); it != mVisitedLevels.end(); ++it)
if (it->level == currentLevel)
{
mVisitedLevels.erase(it);
break;
}
// ajoute une nouvelle entrée
CSnapshot s;
s.level = currentLevel;
s.map = map;
s.copyPressPlates(tiles.getPressPlates());
mVisitedLevels.push_back(s);
}
}
Cette fonction est appelée à chaque fois qu'on charge un nouveau niveau dans la fonction loadLevel():
void CGame::loadLevel(int level, CVec2 pos, int side)
{
// mémorise le niveau courant
snapshotLevel();
currentLevel = level;
// cherche si le nouveau niveau est mémorisé...
for (size_t i = 0; i < mVisitedLevels.size(); ++i)
{
CSnapshot* s = &mVisitedLevels[i];
if (s->level == level)
{
map.freeMemory();
map = s->map;
tiles.setPressPlates(s->pressPlateStates);
player.pos = pos;
player.dir = side;
return;
}
}
// ...sinon, charge le nouveau niveau
static char fileName[256];
sprintf(fileName, "maps/level%02d.map", level);
map.load(fileName);
player.pos = pos;
player.dir = side;
tiles.initMap();
walls.init();
}
Et voila. Maintenant, à chaque fois qu'on prendra un escalier pour revenir à un niveau qu'on a déjà visité, on le