Part 20: Game interface - Part 3: Cursors and arrows
Downloads
The cursors
MouseArea {
anchors.fill: parent
enabled: false
cursorShape: Qt.BlankCursor
}
It is not enabled, so it won't intercept the mouse clicks, but when the mouse is inside it, the cursor is blank.
Cursors areas
enum ECursorTypes
{
eCursor_None = 0,
eCursor_Arrow,
eCursor_Hand
};
struct SMouseArea
{
EMouseAreaTypes type;
CRect rect;
void* param1;
void* param2;
ECursorTypes cursor;
};
[...]
void addArea(EMouseAreaTypes type, CRect rect, ECursorTypes cursor = eCursor_Arrow, void* param1 = NULL, void* param2 = NULL);
Then we have to modify CMouse::clickedArea(). We won't check the areas only when we the mouse is clicked. We will first
SMouseArea* CMouse::clickedArea(QImage* image)
{
SMouseArea* area = NULL;
for (int i = mAreaList.size() - 1; i >= 0; --i)
{
CRect* rect = &mAreaList[i].rect;
if (mPos.x >= rect->tl.x &&
mPos.y >= rect->tl.y &&
mPos.x <= rect->br.x &&
mPos.y <= rect->br.y)
{
area = &mAreaList[i];
break;
}
}
if (area == NULL || area->cursor != eCursor_None)
{
std::string fileName;
if (area == NULL)
fileName = "gfx/interface/CursorArrow.png";
else if (area->cursor == eCursor_Arrow)
fileName = "gfx/interface/CursorArrow.png";
else
fileName = "gfx/interface/CursorHand.png";
QImage cursor = fileCache.getImage(fileName);
graph2D.drawImage(image, mPos, cursor);
}
if (mButtonPressed == true)
{
return area;
}
return NULL;
}
You can see that by default we display the arrow cursor.
void CGame::displayMainView(QImage* image)
{
if (interface.isInInventory() == false)
{
CRect mainRect(CVec2(0, 33), CVec2(MAINVIEW_WIDTH - 1, 33 + MAINVIEW_HEIGHT - 1));
mouse.addArea(eMouseArea_None, mainRect, eCursor_Hand);
The same thing appears in the case of the character's sheet, and in a few other places.
The movement arrows
CInterface::CInterface()
{
[...]
arrowsRects[eArrowTurnLeft] = CRect(CVec2(234, 125), CVec2(261, 145));
arrowsRects[eArrowForward] = CRect(CVec2(263, 125), CVec2(289, 145));
arrowsRects[eArrowTurnRight] = CRect(CVec2(291, 125), CVec2(318, 145));
arrowsRects[eArrowLeft] = CRect(CVec2(234, 147), CVec2(261, 167));
arrowsRects[eArrowBackward] = CRect(CVec2(263, 147), CVec2(289, 167));
arrowsRects[eArrowRight] = CRect(CVec2(291, 147), CVec2(318, 167));
}
In the game, we move not only by clicking on these arrows but by presing keys too.
bool CKeyboard::eventFilter(QObject */*object*/, QEvent *event){
if (event->type() == QEvent::KeyPress)
{
[...]
if (keyEvent->isAutoRepeat() == false)
{
[...]
switch (nativeKey)
{
case 16: // 'Q' on QWERTY, 'A' on AZERTY
tempKeys[eTurnLeft] = true;
break;
case 17: // 'W' on QWERTY, 'Z' on AZERTY
tempKeys[eForward] = true;
[...]
When we will need to check the key, we will call an update function that makes a copy of this array, to avoid that it is modified while we
void CKeyboard::update()
{
for (int i = 0; i < eKeyCount; ++i)
{
keys[i] = tempKeys[i];
tempKeys[i] = false;
}
}
Then we'll check both the mouse clicks and the keyboard in an updateArrows() function:
//------------------------------------------------------------
bool CInterface::isClickingOnArrow(SMouseArea* clickedArea, EMouseAreaTypes type)
{
return (mouse.mButtonPressing && clickedArea != NULL && clickedArea->type == type);
}
//------------------------------------------------------------
void CInterface::updateArrows(SMouseArea* clickedArea)
{
if (isClickingOnArrow(clickedArea, eMouseArea_ArrowForward) == true || keyboard.keys[CKeyboard::eForward] == true)
{
arrowHighlightTime = ARROWS_HIGHLIGHT_TIME;
currentArrow = eArrowForward;
player.moveForward();
}
else if (isClickingOnArrow(clickedArea, eMouseArea_ArrowBackward) == true || keyboard.keys[CKeyboard::eBackward] == true)
{
arrowHighlightTime = ARROWS_HIGHLIGHT_TIME;
currentArrow = eArrowBackward;
player.moveBackward();
}
else if (isClickingOnArrow(clickedArea, eMouseArea_ArrowLeft) == true || keyboard.keys[CKeyboard::eLeft] == true)
{
arrowHighlightTime = ARROWS_HIGHLIGHT_TIME;
currentArrow = eArrowLeft;
player.moveLeft();
}
else if (isClickingOnArrow(clickedArea, eMouseArea_ArrowRight) == true || keyboard.keys[CKeyboard::eRight] == true)
{
arrowHighlightTime = ARROWS_HIGHLIGHT_TIME;
currentArrow = eArrowRight;
player.moveRight();
}
else if (isClickingOnArrow(clickedArea, eMouseArea_ArrowTurnLeft) == true || keyboard.keys[CKeyboard::eTurnLeft] == true)
{
arrowHighlightTime = ARROWS_HIGHLIGHT_TIME;
currentArrow = eArrowTurnLeft;
player.turnLeft();
}
else if (isClickingOnArrow(clickedArea, eMouseArea_ArrowTurnRight) == true || keyboard.keys[CKeyboard::eTurnRight] == true)
{
arrowHighlightTime = ARROWS_HIGHLIGHT_TIME;
currentArrow = eArrowTurnRight;
player.turnRight();
}
}
Gathering both mouse and keyboard at the same place will be useful later to limit the movement when characters will be
Highlighting arrows
void CGraphics2D::Xor(QImage* image, CRect rect, QColor color)
{
for (int y = rect.tl.y; y <= rect.br.y; ++y)
for (int x = rect.tl.x; x <= rect.br.x; ++x)
{
QColor newColor;
QRgb pixel = image->pixel(QPoint(x, y));
newColor.setRed(qRed(pixel) ^ color.red());
newColor.setGreen(qGreen(pixel) ^ color.green());
newColor.setBlue(qBlue(pixel) ^ color.blue());
image->setPixel(QPoint(x, y), newColor.rgba());
}
}
Finally, to sum it up, let's look at the drawArrows() function, where we can see the use of our coordinates array,
void CInterface::drawArrows(QImage* image)
{
QImage arrowsBg = fileCache.getImage("gfx/interface/MovementArrows.png");
CVec2 pos(233, 124);
graph2D.drawImage(image, pos, arrowsBg);
if (isArrowsGreyed() == true)
{
CRect rect(CVec2(233, 124),
CVec2(319, 168));
graph2D.patternRectangle(image, rect);
}
else
{
static const EMouseAreaTypes arrowsMouseAreas[] =
{
eMouseArea_ArrowForward,
eMouseArea_ArrowBackward,
eMouseArea_ArrowLeft,
eMouseArea_ArrowRight,
eMouseArea_ArrowTurnLeft,
eMouseArea_ArrowTurnRight
};
for (int i = 0; i < eArrowsCount; ++i)
{
mouse.addArea(arrowsMouseAreas[i], arrowsRects[i]);
}
if (arrowHighlightTime > 0)
{
arrowHighlightTime--;
graph2D.Xor(image, arrowsRects[currentArrow], MAIN_INTERFACE_COLOR);
}
}
}