我正在使用视觉工作室2010和Qt 4.7(两个窗口)中的c ++组合进行游戏。游戏是战舰的克隆,基于控制台输入。我已经创建了我想要它的gui,在Qt设计器的Qt端,我的gui包含一个10x10的网格布局,使用标签来保存游戏单元的像素图:
我精心命名每个标签以表示其在二维阵列中的位置(即。舰队地图=> F_00
=> F[0,0]
=> F[i],[j]
)。我可以使用属性编辑器手动选择我想要显示的像素图,但我想要一些动态的东西。
我使用更新mapboard类在玩家触发后重绘游戏板,这将继续存储在char数组中。我想使用通用的getupdatearray类型函数更新每个pixmaps。当我们遍历数组时,它将更新当前与各个标签相关联的像素图,以匹配来自阵列的兄弟。 (比如命中F[5][6] = 'X'
,然后当循环到达数组中的那个位置时,它会将F_56处的pixmaps网格更新为等于 hit.png ,将替换为空。 PNG
我知道如何制作可实现此目的的循环,但不确定如何让每个标签的像素图更符合运行时功能与现在编译时(静态)功能的关系。我已经阅读了有关QPainter和另一个处理图像的Qt课程,但仍然很努力。
问你们,如何根据二维数组更新这些pixmaps?
这是我尝试用map.h做的那种伪代码:
#include <QtCore>
#include <QtGui>
// WARNING: PSEUDOCODE, DOES NOT COMPILE
// AT A LOSS ON HOW TO SELECT THE CORRECT LABEL
// MAYBE A CHILD CLASS FOR THAT?
class map {
public:
char updateboard(char mapname, char b[][10]){
for(int i=0;i<10;i++){
for(int j=0;j<10;j++){
char C = b[i][j];
if (C == 'M'){//miss
Qlabel mapname_[i][j](<img src='images/missspace.png'/>);
mapname_[i][j].show();
}
else if(C == 'X'){//hit
Qlabel mapname_[i][j](<img src='images/hitspace.png'/>);
mapname_[i][j].show();
}
else if(C == ' '){//undiscovered space
Qlabel mapname_[i][j](<img src='image/emptyspace.png'/>);
mapname_[i][j].show();
}
}
}
}
};
然后在我的mainwindow.cpp中,我包含了map.h并说:
// calls class function update board
// takes updated array values and replaces old pixmap with new
map.updateboard(T,b[][10]); // target map update
map.updateboard(F,v[][10]); // fleet map update
先谢谢
更新
我已经达到了可以用按钮按下交换pixmaps的程度,但我想创建更具动感的东西。我想使用一个Qstring,我在其中使用附加x y值来放置我想要更改的标签的名称:
TR_position.append(QString::number(xvalue));
当我尝试使用以下方式调用它时:
ui->TR_position->setPixmap(QPixmap(":/images/missspace.png"));
......它显然不起作用。有没有办法输入case,或者使用字符串的内容作为Qlabel名称?
答案 0 :(得分:3)
您手动输入并命名 200个标签小部件?没有人叫你懒。 :)
根据您的更新,您现在know how to use QLabel::setPixmap()
。您需要的是认为从名称中获取QLabel指针,这可能是两件事的组合:
QWidget::findChild从QWidget*
QString
qobject_cast从QLabel*
QWidget
如果你沿着这条路走下去,你最终会得到的结论是:
QWidget* cellWidget = ui->findChild(TR_position);
QLabel* cellLabel = qobject_cast<QLabel*>(cellWidget);
cellLabel->setPixmap(QPixmap(":/images/missspace.png"));
但要小心!这种方法有很多问题。
它很脆弱:如果没有任何具有该名称的小部件(神秘的崩溃)怎么办?或者甚至更糟糕的是,如果有多个带有该名称的小部件,并且这段代码会幸福地忽略那些可能是错误的奇怪情况?
这是糟糕的OOP :虽然有一些不错的案例可以使用动态投射(或“向下转换”),但它通常表明设计存在缺陷。您知道所有QLabel都是QWidgets,但并非所有QWidgets都是QLabel ...因此qobject_cast
调用可能会返回NULL。这只是另一个失败点。有时你无法避免这种情况,但实际上没有理由要求你的程序以这种方式构建。
速度非常慢:按名称搜索小部件本质上是一种天真的递归搜索。如果你为每个网格留出一个单独的小部件框架并且只搜索它,Qt将不得不进行100次字符串比较以找到最后一个元素(在平均情况下为50)。想象一下用循环清除网格......现在你谈的是100 * 50字符串比较!
所有这些都是可以避免的。就像可以使用循环来按名称设置控件上的图像一样,可以首先使用循环来创建窗口小部件。您基本上会在设计工具中将游戏板的区域留空,然后使用代码动态创建控件...使用代码将它们附加到布局...并在2D数组中保存指向它们的指针。 (此时您不会通过标签名称访问它们,您可以在索引电路板时将它们编入索引。)
您可以创建自己的从QLabel派生的类(例如GameCell
类),其中包含您的电路板单元的信息以及与之相关的方法。然后,您不需要与代表您的电路板的数组并行的标签小部件数组。您只需要一个对象数组来处理实现的两个方面。
更新:由于您在评论中询问了具体细节,因此这是一个GameCell类:
class GameCell : public QLabel
{
Q_OBJECT
public:
enum State { Undiscovered, Hit, Miss };
GameCell (QWidget *parent = 0) : QLabel (parent),
currentState (Undiscovered)
{
syncBitmap();
}
State getState() const { return currentState; }
void setState(State newState) {
if (currentState != newState) {
currentState = newState;
syncBitmap();
}
}
private:
void syncBitmap() { // you'd use setPixmap instead of setText
switch (currentState) {
case Undiscovered: setText("U"); break;
case Hit: setText("H"); break;
case Miss: setText("M"); break;
}
}
State currentState;
};
这可以通过像QWidget这样的行为以及维持一个内部状态来实现双重任务。然后GameMap小部件可以使用这些GameCells的QGridLayout:
class GameMap : public QWidget {
Q_OBJECT
public:
static const int Rows = 10;
static const int Columns = 10;
GameMap (QWidget* parent = 0) :
QWidget (parent)
{
layout = new QGridLayout (this);
for (int column = 0; column < Columns; column++) {
for (int row = 0; row < Rows; row++) {
GameCell* cell = new GameCell (this);
cells[column][row] = cell;
layout->addWidget(cell, row, column);
}
}
}
private:
GameCell* cells[Columns][Rows];
QGridLayout* layout;
};
如果您愿意,您可以在设计中留下想要用GameMap小部件填充的空间。或者你可以继续推进并以编程方式完成整个事情。为了简单起见,我只是将两块板彼此相邻,并在对话框的表面上放置一个垂直分隔符:
class Game : public QDialog
{
Q_OBJECT
public:
Game (QWidget *parent = 0)
: QDialog(parent)
{
targetMap = new GameMap (this);
fleetMap = new GameMap (this);
verticalSeparator = new QFrame (this);
verticalSeparator->setFrameShape(QFrame::VLine);
verticalSeparator->setFrameShadow(QFrame::Sunken);
layout = new QHBoxLayout (this);
layout->addWidget(targetMap);
layout->addWidget(verticalSeparator);
layout->addWidget(fleetMap);
setLayout(layout);
setWindowTitle(tr("Battleship"));
}
private:
GameMap* targetMap;
QFrame* verticalSeparator;
GameMap* fleetMap;
QHBoxLayout* layout;
};
我不打算在这里写一整场比赛或让它看起来很花哨。这只是一个要点,展示了如何以编程方式获得200个标签:
使用我的代码,从(x,y)坐标获取GameCell不需要平均50个字符串比较。由于2D数组的形式化和可预测性,索引到cells[x][y]
只需要单个乘法运算和单个加法运算。没有向下转换,您只需写下:
cells[x][y].setState(GameCell::Miss);
ADDENDUM :为每个网格单元创建一个QWidget不一定是首先要做的事情。有些人可能会认为“重量级”。如果你的游戏是在一个巨大的虚拟空间上玩,那么它可能会太慢。您可能会发现查看QGraphicsGridLayout很有用,从长远来看,这可能是一种更合适的方法:
http://doc.qt.io/qt-5/qtwidgets-graphicsview-basicgraphicslayouts-example.html
使用QWidgets对于10x10网格来说不是什么大问题,所以如果你想坚持下去就可以了。如果你打算这样做,那么至少你不应该把它们全部放在手边!