Part 29: Stairs and map snapshot
Downloads
Stairs in the editor
<tile name="Stairs">
<image>Stairs.png</image>
<param type="int">Level</param>
<param type="int">X</param>
<param type="int">Y</param>
<param type="enum" values="Up;Left;Down;Right">Side</param>
</tile>
And the wall will give the orientation of the graphics. I chose to use the "back" wall of the stairs - the one that
Stairs in the game
struct SStairsData
{
char file[32];
uint16_t x, y;
};
// stairs going down, facing table
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
};
// stairs going up, facing table
static const SStairsData gUpFacingStairs[WALL_TABLE_HEIGHT][WALL_TABLE_WIDTH] =
{
[...]
};
// stairs going down, side table
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
};
// stairs going up, side table
static const SStairsData gUpSideStairs[WALL_TABLE_HEIGHT][WALL_TABLE_WIDTH] =
{
[...]
};
void CTiles::drawStairs(QImage* image, CTile* tile, CVec2 tablePos)
{
const SStairsData* data;
// find the side of the stairs
EWallSide side;
int i;
for (i = 0; i < 4; ++i)
if (tile->mWalls[i].getType() == eWallStairs)
break;
side = (EWallSide)i;
bool isDown = (tile->getIntParam("Level") > game.currentLevel);
if (side != player.getWallSide(eWallSideDown))
{
// get the table data
if (side == player.getWallSide(eWallSideUp))
{
// facing stairs
if (isDown)
data = &gDownFacingStairs[tablePos.y][tablePos.x];
else
data = &gUpFacingStairs[tablePos.y][tablePos.x];
}
else
{
// side stairs
if (isDown)
data = &gDownSideStairs[tablePos.y][tablePos.x];
else
data = &gUpSideStairs[tablePos.y][tablePos.x];
}
// display
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))
{
// special case when we are on the stairs' tile
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);
}
}
}
Taking the stairs
void CPlayer::takeStairs()
{
CTile* tile = map.getTile(pos);
int level = tile->getIntParam("Level");
CVec2 newPos = CVec2(tile->getIntParam("X"), tile->getIntParam("Y"));
int side = tile->getEnumParam("Side");
game.loadLevel(level, newPos, side);
}
The CGame::loadLevel() function obviously loads the new level and set the position of the player in the new 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;
}
}
And at the end of the game loop, we test this flag to call our function:
QImage& CGame::mainLoop()
{
[...]
if (player.shouldTakeStairs)
player.takeStairs();
return image;
}
And we don't forget to set this flag back to false in takeStairs().
Map snapshot
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;
};
The datas we will store for each map are:
CMap::CMap(const CMap& rhs)
{
mTiles = NULL;
*this = rhs;
}
So I had to write copy constructors for a couple of classes, and by the way I also wrote proper "=" operators for
void CGame::snapshotLevel()
{
if (currentLevel != 0)
{
// if there is an entry for this level, delete it
std::vector<CSnapshot>::iterator it;
for (it = mVisitedLevels.begin(); it != mVisitedLevels.end(); ++it)
if (it->level == currentLevel)
{
mVisitedLevels.erase(it);
break;
}
// add a new entry
CSnapshot s;
s.level = currentLevel;
s.map = map;
s.copyPressPlates(tiles.getPressPlates());
mVisitedLevels.push_back(s);
}
}
This function is called every time we load a new level in the loadLevel() function:
void CGame::loadLevel(int level, CVec2 pos, int side)
{
// snapshot the current level
snapshotLevel();
currentLevel = level;
// search if the new level was snapshoted...
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;
}
}
// ...else load the new level
static char fileName[256];
sprintf(fileName, "maps/level%02d.map", level);
map.load(fileName);
player.pos = pos;
player.dir = side;
tiles.initMap();
walls.init();
}
And that's it. Now, every time we take the stairs to get back to a level we previously visited,