
本文共 4381 字,大约阅读时间需要 14 分钟。
通过一个经典的游戏——扫雷,我对Qt的认识得到了进一步提升。以下是基于Qt的扫雷游戏开发过程中的关键技术和实现细节。
一、游戏基本组件
扫雷游戏的核心组件是雷区,即游戏中的每个小方格。这些方格可以看作是图元,图元的状态由多个参数决定,包括是否被挖掘、是否被标记等。鼠标点击某个图元会触发其鼠标函数,从而改变其状态。
图元类myItem
图元类myItem双继承于QObject和QGraphicsPixmapItem。通过将图元放入场景QGraphicsScene中,并设置UI组件QGraphicsView的场景,才能显示出一系列图元。
class myItem : public QObject, public QGraphicsPixmapItem { Q_OBJECT //省略其他函数和信号protected: void mousePressEvent(QGraphicsSceneMouseEvent *event) override;private: int row; // 行号 int col; // 列号 bool swept = false; // 是否已被挖掘 bool mine = false; // 是否有地雷 bool mark = false; // 是否有标记 bool stepMine = false; // 是否踩雷 int numOfMines = 0; // 周围地雷个数};
鼓励信号机制
当左击一个没有地雷且周围地雷数为0的方格时,会自动打开周围所有格子。这个过程通过递归实现。
void myItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (event->buttons() == (Qt::LeftButton | Qt::RightButton)) { if ((swept && numOfMines != 0) || (!swept && mark)) { emit doubleClickSignal(row, col); } } else if (event->button() == Qt::LeftButton) { simulateLeftClick(); } else if (event->button() == Qt::RightButton) { if (!swept) { setMark(!mark); emit markChangedSignal(); } } emit checkSignal(); // 检测游戏是否成功}
二、场景类myScene
场景类myScene包含一个myItem类对象指针的二维数组,用于管理所有图元。
class myScene : public QObject, public QGraphicsScene { //省略其他函数和信号private: static const int MAX_HEIGHT = 24; static const int MAX_WIDTH = 30; myItem *items[MAX_WIDTH][MAX_HEIGHT]; int mine = 10; // 地雷数 int height = 9; // 地图宽高 int width = 9; // 地图宽高};
三、核心功能findBlock()
findBlock()函数是扫雷的核心逻辑。当左击一个没有地雷且周围地雷数为0的方格时,会自动打开周围所有方格。
void myScene::findBlocks(int row, int col) { items[row][col]->setSwept(true); if (items[row][col]->getNumOfMines() != 0) return; int nx, ny; int dx[8] = { -1, -1, -1, 0, 0, 1, 1, 1 }; int dy[8] = { -1, 0, 1, -1, 1, -1, 0, 1 }; for (int i = 0; i < 8; ++i) { nx = row + dx[i]; ny = col + dy[i]; if (0 <= nx && nx < width && 0 <= ny && ny < height) { if (!items[nx][ny]->isSwept()) { findBlocks(nx, ny); } } }}
四、界面细节
1. 设置场景大小与视图显示一致
scene的大小由添加的图元大小决定。每个图元大小为20x20,scene大小为20x20。当scene大小与view大小接近时,设置view的大小略大于scene即可。
2. 去掉滚动框
this->ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);this->ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
3. 界面布局
不建议使用布局管理,直接使用setGeometry和setFixedSize设置位置和固定大小即可。
4. 自定义地图设置
写一个自定义函数customSet(int width, int height, int mine),用于方便调用。
void MainWindow::on_actionPrimary_triggered() { customSet(9, 9, 10);}void MainWindow::on_actionMid_triggered() { customSet(16, 16, 40);}void MainWindow::on_actionSenior_triggered() { customSet(30, 16, 99);}
5. 数字显示屏
设计一个numItem图元类,用于显示3位数字。通过调用numScene类即可实现双数字显示。
void numItem::setNum(int a) { if (a == 0) { setPixmap(QPixmap(":/num/pic/num0.png")); } else if (a == 1) { setPixmap(QPixmap(":/num/pic/num1.png")); } else if (a == 2) { setPixmap(QPixmap(":/num/pic/num2.png")); } else if (a == 3) { setPixmap(QPixmap(":/num/pic/num3.png")); } else if (a == 4) { setPixmap(QPixmap(":/num/pic/num4.png")); } else if (a == 5) { setPixmap(QPixmap(":/num/pic/num5.png")); } else if (a == 6) { setPixmap(QPixmap(":/num/pic/num6.png")); } else if (a == 7) { setPixmap(QPixmap(":num/pic/num7.png")); } else if (a == 8) { setPixmap(QPixmap(":num/pic/num8.png")); } else if (a == 9) { setPixmap(QPixmap(":num/pic/num9.png")); }}void numScene::setNumer(int num) { if (num >= 0) { this->clear(); int hundred, ten, one; hundred = num / 100; ten = (num % 100) / 10; one = num % 10; numItem *hunItem, *tenItem, *oneItem; hunItem = new numItem; tenItem = new numItem; oneItem = new numItem; hunItem->setPos(1, 0); tenItem->setPos(20, 0); oneItem->setPos(39, 0); hunItem->setNum(hundred); tenItem->setNum(ten); oneItem->setNum(one); this->addItem(hunItem); this->addItem(tenItem); this->addItem(oneItem); }}
通过这个经典的小游戏,我对Qt的理解得到了显著提升。从图元类到场景类,从信号机制到界面优化,每一个环节都让我对Qt的强大功能有了更深的体会。这次项目不仅让我掌握了扫雷游戏的开发技巧,更让我对 Qt 的信号与槽机制、场景与图元的管理有了更深入的理解,为后续的GUI开发打下了坚实的基础。
发表评论
最新留言
关于作者
