Source code
Executable for the map editor (Windows 32bits)
Executable for the game - exactly the same as in part 7 (Windows 32bits)
Before you try to compile the code go to the "Projects" tab on the left menu, select the "Run" settings for your kit,
and set the "Working directory" to the path of "editor\data" for the editor or "game\data" for the game.
I will leave the game alone for a while and concentrate on the editor. My plan is to improve it and make it easier to
use before I try to reproduce a level of the original game. Then we will add the different game elements as they appear.
I added a few buttons for the tools in part 3, now it's time to make them work.
So we'll see in this part the selection tool, the undo/redo duo and the copy/cut/paste trio.
First, if you look at "main.qml" you can see that I added an "Edit" menu which contains copy, cut, paste, undo and redo as
in most PC softwares.
In QML it's an easy way to intercept the standard keyboard shortcuts associated with those functions.
You will also notice that I added 3 lines for the copy/cut/paste functions.
As before, those lines mainly call functions in bridge.cpp that only holds the values for the C++ part and calls a few
functions in editor.cpp.
For the undo/redo functions I used a Qt mechanism called QUndoStack. In fact it is only a stack that stores the commands
we add to it. You could easily do the same thing with a linked list.
To use it we have to create a class for each command that inherits a QUndoCommand class.
The idea is that each of those commands knows how to undo and redo its action and stores the datas it need to do that.
I have put all those classes in a new file "tools.cpp" - and it's corresponding ".h" file.
For now we only had one drawing tool. For clarity I splitted it in a drawTile and a drawWall tools.
So let's see what the drawTile looks like.
From tools.h:
class drawTileTool : public QUndoCommand
{
public:
explicit drawTileTool(QUndoCommand *parent = 0);
bool init(CVec2 pos, int type);
void undo() override;
void redo() override;
private:
CVec2 mPos;
int mType;
int mOldType;
};
From tools.cpp:
drawTileTool::drawTileTool(QUndoCommand *parent)
: QUndoCommand(parent)
{
}
bool drawTileTool::init(CVec2 pos, int type)
{
mPos = pos / TILE_SIZE;
CTile* tile = map.getTile(mPos);
if (tile == NULL)
return false;
if (tile->mType == type)
return false;
mOldType = tile->mType;
mType = type;
return true;
}
void drawTileTool::undo()
{
map.setTile(mPos, mOldType);
bridge.updateQMLImage();
}
void drawTileTool::redo()
{
map.setTile(mPos, mType);
bridge.updateQMLImage();
}
In the constructor we do nothing except that we pass the parameter "parent" to the QUndoCommand. This parameter is used to link several commands
together, but we won't use this functionnality.
I choosed to add an init() function in each command. It sets the datas we will need later and it returns a bool that tells if we really need
to execute this command and store it on the stack.
So in this case, we store the position of the tile we want to modify in mPos, the type we want to set to this tile in mType, and the "old" type that
the tile contains before that we execute the command in mOldType.
As when we press the mouse button, the drawing tool is called every time we move the mouse, we don't want to store useless commands while we are
still on the same tile. So if the tile already has the type we want to set, we return "false".
The undo and redo functions are pretty simple to understand: we set the tile to the desired type and we redraw the map.
Now let's see how the command is called and added to the undo stack. Somewhere near the end of "editor.cpp" you will find these lines:
// tiles
drawTileTool* tool = new drawTileTool();
int type = 0;
if (bridge.rightPressed == true)
type = 0;
else if (bridge.leftPressed == true)
type = 1;
if (tool->init(bridge.mMousePos, type) == true)
{
unselect();
mUndoStack->push(tool);
}
else
delete tool;
We create a new drawTileTool instance.
We decide which type we want to set depending on which mouse button is pressed.
We call the init() function to set up the variables.
If the init() function returns "true", we push the command on the stack. Note that when we push it, its redo() function is automatically
called. So the command is really applied to the map.
If the init() function returns "false", then we simply delete the drawTileTool instance.
The unselect() function is related to the selection tool that we will see right now.
As you can see in the "tools.*" files this undo mechanism is used for all the functions that modifies the map.
I already talked about the selection tool in part 3. It allows you to select a tile or a wall - soon we will add a box with informations
about the selected item.
And when you drag the mouse you can also select a rectangle of tiles.
These selections will be used with the cut and the copy tools.
But to draw the selections I had to create some functions to reuse the drawing routines of the cursor.
Now "editor.cpp" mostly contains graphics functions to draw rectangles. In the middle of all those functions you can find drawCursor():
void CEditor::drawCursor(QImage* image, CVec2 mousePos, ETabIndex tabIndex)
{
if (mousePos.x != -1)
{
if (tabIndex == eTabTiles)
drawTileRect(image, mousePos, CURSOR_COLOR);
else if (tabIndex == eTabWalls)
drawWallRect(image, mousePos, CURSOR_COLOR);
else if (tabIndex == eTabObjects)
drawObjectRect(image, mousePos, CURSOR_COLOR);
}
}
As you can see it calls drawTileRect(), drawWallRect() or drawObjectRect() depending on the tab we selected in the tabView.
And you can see a very similar function to draw the selection:
void CEditor::drawSelection(QImage* image, ETabIndex tabIndex)
{
if (mSelectStart.x != -1)
{
if (tabIndex == eTabTiles)
drawTileSelection(image);
else if (tabIndex == eTabWalls)
drawWallSelection(image);
else if (tabIndex == eTabObjects)
drawObectSelection(image);
}
}
Those 3 functions drawTileSelection(), drawWallSelection() and drawObectSelection() call the same functions as the cursor
drawTileRect(), drawWallRect() and drawObjectRect() if we selected only one element or they call drawRectangleSelection() if we
selected a rectangle of several tiles.
The basis of these functions is the CClipboard class that is in the files "clipboard.*".
This class only stores a rectangular array of tiles and contains functions to copy if to/from the map or from another clipboard class.
The copy tool is simple. We only copy the data from the map into the clipboard structure.
The cut tool is a little bit different as it modifies the map - it clears the selected area. So it has a class in tools.cpp as we need
to be able to "undo" this modification.
The paste tool works like the drawing ot the selection tools. We first select the tool, then we have to click on the map to paste the selection.
At the moment, only a rectangle appears to show the place where the selection will be pasted. In the future I will perhabs add a "preview"
of the tiles or walls that will be modified.
For now there is still a little bug: you can't copy a single wall. The copy/cut/paste operations are applied to the whole tile where the wall is.