Part 14: Game interface - Part 2: Faces, hands, text and mouse
Downloads
Code cleaning
void CGame::drawWall(QImage* image, CVec2 mapPos, CVec2 tablePos, EWallSide side, bool flip)
{
CTile* tile = map.getTile(mapPos);
if (tile != NULL)
{
EWallSide playerSide = player.getWallSide(side);
if (tile->mWalls[playerSide].getType() != eWallNothing)
{
const char* fileName = walls.getFilename(tablePos, side, flip);
if (fileName[0] != 0)
{
// draw the wall
QImage wallGfx = fileCache.getImage(fileName);
CVec2 gfxPos = walls.getGfxPos(tablePos, side, flip);
graph2D.drawImage(image, gfxPos, wallGfx, 0, flip);
// draw the ornates
CWall* wall = &tile->mWalls[playerSide];
if (wall->getType() == eWallSimple)
{
int ornate = ((CParamOrnate*)wall->mParams[0])->mValue;
walls.drawOrnate(image, tablePos, side, ornate);
}
}
}
}
}
If you look at the drawOrnate() function you will also notice that I moved the part that darkens the image to another function
Atlasing
void CInterface::drawChampion(QImage* image, int num)
{
if (isChampionEmpty(num) == false)
{
[...]
if (isChampionDead(num) == true)
{
[...]
}
else
{
QImage champBg = fileCache.getImage("gfx/interface/ChampInfo.png");
CVec2 pos(69 * num, 0);
graph2D.drawImage(image, pos, champBg);
if (isInInventory() == true && currentChampion == num)
{
// draw a background rectangle
CVec2 pos2(69 * num, 0);
CRect rect2(pos2, CVec2(pos2.x + 42, 6));
graph2D.rect(image, rect2, QColor(68, 68, 68), true);
CVec2 pos(69 * num + 7, 0);
CRect rect(pos, CVec2(pos.x + CHAMP_PORTRAIT_WIDTH - 1, pos.y + CHAMP_PORTRAIT_HEIGHT - 1));
graph2D.rect(image, rect, QColor(102, 102, 102), true);
// draw head
CCharacter* c = &game.characters[num];
rect = getChampionPortraitRect(c->picture);
QImage portraits = fileCache.getImage("gfx/interface/ChampPortraits.png");
graph2D.drawImageAtlas(image, pos, portraits, rect);
[...]
}
else
{
// draw rectangles
CRect rect(CVec2(69 * num + 3, 9),
CVec2(69 * num + 20, 26));
graph2D.rect(image, rect, QColor(102, 102, 102), false);
CRect rect2(CVec2(69 * num + 23, 9),
CVec2(69 * num + 40, 26));
graph2D.rect(image, rect2, QColor(102, 102, 102), false);
// draw hands
QImage bodyParts = fileCache.getImage("gfx/interface/Items6.png");
rect = getBodyPartRect(CCharacter::eBodyPartLeftHand);
CVec2 pos(69 * num + 4, 10);
graph2D.drawImageAtlas(image, pos, bodyParts, rect);
rect = getBodyPartRect(CCharacter::eBodyPartRightHand);
CVec2 pos2(69 * num + 24, 10);
graph2D.drawImageAtlas(image, pos2, bodyParts, rect);
[...]
}
// bars
drawBar(image, num, CVec2(69 * num + 46, 2), CCharacter::eStatHealth);
drawBar(image, num, CVec2(69 * num + 53, 2), CCharacter::eStatStamina);
drawBar(image, num, CVec2(69 * num + 60, 2), CCharacter::eStatMana);
}
}
}
The texts
void CInterface::drawText(QImage* image, CVec2 pos, EFonts font, const char* text, QColor color)
{
// Sprite number tables for each character in ASCII from char 32 to char 120
// The lowercase letters are used for the spells
// !"#$%&'()*+,-./
// 0123456789:;<=>?
// @ABCDEFGHIJKLMNO
// PQRSTUVWXYZ[\]^_
// `abcdefghijklmno
// pqrstuvwx
// eFontStandard
static const int codesStandard[] =
{
// ! " # $ % & ' ( ) * + , - . /
0x00, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
[...]
};
// eFontScroll
static const int codesScroll[] =
{
// ! " # $ % & ' ( ) * + , - . /
0x00, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x1D, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
[...]
};
// eFontWalls
static const int codesWalls[] =
{
// ! " # $ % & ' ( ) * + , - . /
0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x21, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1B, 0x1A,
[...]
};
const int* codes;
int offset;
QImage file;
if (font == eFontStandard)
{
codes = codesStandard;
file = fileCache.getImage("gfx/interface/Font.png");
offset = -2;
pos.x -= 3;
}
else if (font == eFontScroll)
{
codes = codesScroll;
file = fileCache.getImage("gfx/interface/Font.png");
offset = -2;
pos.x -= 3;
}
else
{
codes = codesWalls;
file = fileCache.getImage("gfx/3DView/WallFont.png");
offset = 0;
}
int i = 0;
while (text[i] != 0)
{
int c = codes[text[i] - 32];
CRect rect(CVec2(c * 8, 0),
CVec2(c * 8 + 7, file.height() - 1));
CVec2 cPos(pos.x + i * (8 + offset), pos.y);
graph2D.drawImageAtlas(image, cPos, file, rect, (font != eFontWalls ? color : QColor(0, 0, 0, 0)));
i++;
}
}
Putting it all together, we finally get something like that:
The mouse
Rectangle {
width: 320
height: 200
color: "black"
property alias image1: image1
property alias mouseArea1: mouseArea1
Image {
id: image1
anchors.fill: parent
fillMode: Image.PreserveAspectFit
cache: false
clip: true
smooth: false
horizontalAlignment: Image.AlignHCenter
verticalAlignment: Image.AlignVCenter
source: "image://myimageprovider/0"
MouseArea {
id: mouseArea1
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
}
}
}
As we do in the editor, in "main.qml" we intercept the mouse moves and click to send it to a bridge.cpp file:
mouseArea1.onMouseXChanged: bridge.setMouse(mouseArea1.mouseX, mouseArea1.mouseY, mouseArea1.width, mouseArea1.height);
mouseArea1.onMouseYChanged: bridge.setMouse(mouseArea1.mouseX, mouseArea1.mouseY, mouseArea1.width, mouseArea1.height);
mouseArea1.onPressed:bridge.setMousePress(mouse.button);
mouseArea1.onReleased: bridge.setMouseRelease(mouse.button);
Then we store these datas in "bridge.cpp".
void CBridge::setMouse(qint32 x, qint32 y, qint32 windowWidth, qint32 windowHeight)
{
CVec2 imagePos;
CVec2 imageSize;
float widthScale = (float)windowWidth / (float)SCREEN_WIDTH;
float heightScale = (float)windowHeight / (float)SCREEN_HEIGHT;
if (widthScale <= heightScale)
{
imageSize.x = (int32_t)windowWidth;
imageSize.y = (int32_t)(widthScale * (float)SCREEN_HEIGHT);
imagePos.x = 0;
imagePos.y = (windowHeight - imageSize.y) / 2;
}
else
{
imageSize.x = (int32_t)(heightScale * (float)SCREEN_WIDTH);
imageSize.y = (int32_t)windowHeight;
imagePos.x = (windowWidth - imageSize.x) / 2;
imagePos.y = 0;
}
mMousePos = CVec2(x, y) - imagePos;
mMousePos.x = (mMousePos.x * SCREEN_WIDTH) / imageSize.x;
mMousePos.y = (mMousePos.y * SCREEN_HEIGHT) / imageSize.y;
}
void CBridge::setMousePress(qint32 button)
{
if (button == Qt::LeftButton)
mMouseButton = true;
}
void CBridge::setMouseRelease(qint32 button)
{
if (button == Qt::LeftButton)
mMouseButton = false;
}
The game loop
QImage CGame::mainLoop()
{
QImage image(SCREEN_WIDTH,
SCREEN_HEIGHT,
QImage::Format_RGBA8888);
mouse.clearAreas();
// clear the screen
image.fill(QColor(0, 0, 0));
// display
displayMainView(&image);
interface.display(&image);
// processing
mouse.update();
SMouseArea* clickedArea = mouse.clickedArea();
interface.update(clickedArea);
return image;
}
At the time of the Amiga and Atari, every game had this architecture: a main function that was called 50 or 60
The mouse - part 2
#ifndef MOUSE_H
#define MOUSE_H
#include "../common/sources/system/cmaths.h"
#include <vector>
enum EMouseAreaTypes
{
eMouseArea_Inventory = 0,
eMouseArea_3DView
};
struct SMouseArea
{
EMouseAreaTypes type;
CRect rect;
void* param1;
void* param2;
};
class CMouse
{
public:
CMouse();
void update();
void clearAreas();
void addArea(EMouseAreaTypes type, CRect rect, void* param1 = NULL, void* param2 = NULL);
SMouseArea* clickedArea();
CVec2 mPos;
bool mButtonPressed;
bool mButtonPressing;
bool mButtonReleasing;
private:
std::vector<SMouseArea> mAreaList;
};
extern CMouse mouse;
#endif // MOUSE_H
First, let's talk about the update() function.
void CInterface::update(SMouseArea* clickedArea)
{
if (clickedArea != NULL)
{
if (mouse.mButtonPressing == true)
{
switch (clickedArea->type)
{
case eMouseArea_Inventory:
mainState = eMainInventory;
currentChampion = (int)clickedArea->param1;
break;
case eMouseArea_3DView:
mainState = eMainGame;
break;
default:
break;
}
}
}
}
And a little bit more...