2

我正在使用 Visual Studio 2010 和 Qt 4.7(两个窗口)中的 c++ 组合进行游戏。该游戏是战舰的克隆,基于控制台输入。我已经创建了我想要的 gui,在 Qt 设计器的 Qt 端,我的 gui 包含一个 10x10 的网格布局,使用标签来保存游戏单元的像素图:

游戏的当前外观

我煞费苦心地命名每个标签来表示它在二维数组中的位置(即车队地图 => F_00=> F[0,0]=> F[i],[j])。我可以使用属性编辑器手动选择我想显示的像素图,但我想要一些动态的。

我使用更新地图板类在玩家开火后重新绘制游戏板,它继续存储在一个 char 数组上。我想使用通用的 getupdatearray 类型函数为每个更新我的像素图。当我们遍历数组时,它将更新当前与各个标签关联的像素图,以匹配数组中的表兄弟。(比如说F[5][6] = 'X'命中,然后当循环到达数组中的那个位置时,它会将 F_56 处的像素图网格更新为等于hit.png,替换empty.png

我有一个想法如何制作可以完成此任务的循环,但不确定如何让每个标签的像素图更符合运行时功能而不是现在的编译时(静态)功能。我读过关于 QPainter 和另一个处理图像的 Qt 类,但仍然很难做到。

对你们任何人的问题,我如何根据二维数组更新这些像素图?

  1. 循环结构-我可以弄清楚
  2. 条件语句 - 我可以弄清楚
  3. qt 处理标签的特定语法 - 新手所以不知道 atm。

这是我试图用 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

提前致谢

更新:

我已经到了可以用按钮交换像素图的地步,但我想创造一些更动态的东西。我想使用一个Qstring,通过使用以下方法附加xy值来放置我想要更改的标签的名称:

TR_position.append(QString::number(xvalue));

当我尝试使用以下方法调用它时:

ui->TR_position->setPixmap(QPixmap(":/images/missspace.png"));

……这显然行不通。有没有办法输入大小写,或者使用字符串的内容作为 Qlabel 名称?

4

1 回答 1

4

您手动输入并命名了200 个标签小部件?不要让任何人说你懒惰。:)

根据您的更新,您现在知道如何使用QLabel::setPixmap(). 您认为您需要的是从名称中获取 QLabel 指针,这将是两件事的组合:

如果你沿着这条路走下去,你最终会得到如下结果:

QWidget* cellWidget = ui->findChild(TR_position);
QLabel* cellLabel = qobject_cast<QLabel*>(cellWidget);
cellLabel->setPixmap(QPixmap(":/images/missspace.png"));

但当心!这种方法有很多问题。

  • 这很脆弱:如果碰巧没有任何具有该名称的小部件(神秘崩溃)怎么办?或者更糟糕的是,如果有多个具有该名称的小部件,并且这段代码幸福地前进,却不知道可能是错误的奇怪情况怎么办?

  • 这是糟糕的 OOP:虽然有一些不错的案例使用动态转换(或“向下转换”),但它通常表明设计中存在缺陷。您知道所有的 QLabel 都是 QWidget,但并非所有的 QWidget 都是 QLabel……所以qobject_cast调用可能会返回 NULL。这只是一个失败点。有时您无法避免这种情况,但实际上没有理由需要以这种方式构建您的程序。

  • 这非常慢:按名称搜索小部件本质上是一种简单的递归搜索。如果您为每个网格留出了一个单独的小部件框架并且只搜索它,Qt 将不得不进行 100 次字符串比较才能找到最后一个元素(平均情况下为 50 次)。想象一下用循环清除网格......现在您正在谈论 100*50 字符串比较!

所有这些事情都是可以避免的。正如可以使用循环按名称设置控件上的图像一样,可以使用循环首先创建小部件。您基本上会将设计工具中的游戏板区域留空,然后使用代码动态创建控件...使用代码将它们附加到布局...并将指向它们的指针保存在二维数组中。(此时您不会通过标签名称访问它们,您会像索引板一样索引它们。)

您可以创建自己的从 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 次字符串比较。由于二维数组的形式化和可预测性,索引cells[x][y]只需要一个乘法运算和一个加法运算。没有向下转换,你可以简单地写:

cells[x][y].setState(GameCell::Miss);

附录:为每个网格单元创建一个 QWidget 不一定是首先要走的路。有些人可能认为这是“重量级”。如果您的游戏是在一个巨大的虚拟瓷砖空间上进行的,那么它可能会太慢。您可能会发现查看QGraphicsGridLayout很有用,从长远来看,这可能是一种更合适的方法:

http://doc.qt.io/qt-5/qtwidgets-graphicsview-basicgraphicslayouts-example.html

但是,对于 10x10 网格,使用 QWidgets 不会有太大问题,因此,如果您只想坚持使用它,那么您可以。如果您打算这样做,那么至少您不应该将它们全部手动放置!

于 2011-12-01T01:10:26.717 回答