思路分析
qt的学习中,可以试着写一写感兴趣的东西,比如小游戏。贪吃蛇、扫雷、俄罗斯方块等小游戏是很好的练习目标,本文讲讲扫雷的小游戏实现过程及思路。
首先思考下扫雷用到了什么东西?鼠标点击、画面显示、音频等等,简单的做一个鼠标点击与画面显示的扫雷。鼠标点击与画面显示在qt中可以使用事件来完成,对应的是鼠标点击事件与图像绘制事件,即QMouseEvent和QPainter,这两个可以查询qt事件来学习。
扫雷的逻辑大概可以简述为:鼠标左键点击方格,不是雷显示空白区域或者数字区域,点中雷即游戏失败。邮件插小旗子,再次点击右键取消小旗子。刚才的逻辑中涉及到随机数的生成,雷的生成使用随机数生成,根据难度生成不同数量的雷,本文以简单的10颗雷为例子。数字是根据一个方格周围的8个方格中雷的数量来显示数字大小,没有雷不显示。
游戏胜利:旗子所插的位置跟雷所在位置完全一致,游戏成功!
现在开始代码讲解!
代码讲解
1、工程文件
没有用到需要添加功能块的类,直接新建一个工程文件即可
2、头文件
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QPainter>
#include <QVector>
#include <random>
#include <QRandomGenerator>
#include <QDebug>
#include <QMouseEvent>
#include <QMessageBox>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
int random_boom_i;
int random_boom_j;
QVector<QPoint> mines;//雷点
int mouseclicked=0;
bool flag = false;
bool isflag = false;
QVector<QPoint> flagPainter;//旗子显示点
QVector<QPoint> isflagPainter;//取消旗子显示点
bool iswin = false;
//初始化函数
void initmines(void);
protected:
void mousePressEvent(QMouseEvent *event);
void paintEvent(QPaintEvent *event);
void boomLocation(void);//坤坤位置
void promptNumber(void);//周围数字提示
};
#endif // WIDGET_H
头文件定义了一些变量与引用了些类,类的使用可以自行查阅帮助文件查找(要形成自己查阅帮助文件的好习惯),变量名字中式直译即可知道代表什么。
3、cpp文件
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
boomLocation();//坤坤生成
}
Widget::~Widget()
{
delete ui;
}
void Widget::initmines()
{
mines.clear();
mouseclicked=0;
flag = false;
isflag = false;
flagPainter.clear();
isflagPainter.clear();
iswin = false;
boomLocation();
update();
}
void Widget::mousePressEvent(QMouseEvent *event)
{
//点中坤坤即失败
int mines_x = event->x();
int mines_y = event->y();
for (int i=0;i<9;i++)
{
for (int j=0;j<9;j++)
{
if((25+i*50+i<=mines_x)&&(75+i*50+i>=mines_x)&&(75+j*50+j<=mines_y)&&(125+j*50+j>=mines_y))
{
mines_x = 25+i*50+i;
mines_y = 75+j*50+j;
}
}
}
QPoint mines_pos(mines_x, mines_y);
//显示数字
if(!mines.contains(mines_pos)&&(event->button() == Qt::LeftButton))
{
mouseclicked = 2;
update();
}
//插旗显示旗子图标
if((event->button() == Qt::RightButton)&&(!flagPainter.contains(QPoint(mines_x, mines_y)))&&(flagPainter.size() < 10))
{
flag = true;
flagPainter.append(QPoint(mines_x, mines_y));
isflagPainter.removeOne(QPoint(mines_x, mines_y));
update();
}
//取消旗子显示
else if((event->button() == Qt::RightButton)&&(flagPainter.contains(QPoint(mines_x, mines_y))))
{
flag = true;
isflag = true;
flagPainter.removeOne(QPoint(mines_x, mines_y));
isflagPainter.append(QPoint(mines_x, mines_y));
update();
}
//GAMEOVER
if(mines.contains(mines_pos)&&(event->button() == Qt::LeftButton)&&(!flagPainter.contains(QPoint(mines_x, mines_y))))
{
mouseclicked = 1;
update();
QMessageBox::critical(this, "Gameover", "游戏结束,请重新开始!!!");
initmines();//初始化
}
//win
for (int i=0;i < mines.size();i++)
{
int wincount;
for (int j=0;j < flagPainter.size();j++)
{
(mines[i] == flagPainter[j]) ? ++wincount : wincount;
}
if(wincount == mines.size())
{
iswin = true;
}
}
if(iswin)
{
QMessageBox::information(this, "结算画面", "YOU WIN THE GAME!!!!");
initmines();//初始化
}
}
void Widget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
QPainter painter(this);
//底图
// if(mouseclicked == 0)
// {
for (int i=0;i<9;i++)
{
for (int j=0;j<9;j++)
{
QPixmap pix;
pix.load(":/img/nofan.png");//底图小方块
painter.drawPixmap(25+i*50+i,75+j*50+j,pix);
}
}
// }
//坤坤
if(mouseclicked == 1)
{
QPixmap pixmap;
pixmap.load(":/img/ikun.jpg");
pixmap = pixmap.scaled(50, 50);
for (int i=0;i<10;i++)
{
painter.drawPixmap(mines[i], pixmap);
}
}
//数字
if(mouseclicked == 2)
{
for (int i=0;i<9;i++)
{
for (int j=0;j<9;j++)
{
int num = 0;
//考虑嵌套4个for消耗资源过多,使用if的方法写
if(!mines.contains(QPoint(25+i*50+i,75+j*50+j)))
{
mines.contains(QPoint(25+i*50+i,75+(j+1)*50+(j+1))) ? ++num : num;
mines.contains(QPoint(25+i*50+i,75+(j-1)*50+(j-1))) ? ++num : num;
mines.contains(QPoint(25+(i-1)*50+(i-1),75+j*50+j)) ? ++num : num;
mines.contains(QPoint(25+(i-1)*50+(i-1),75+(j+1)*50+(j+1))) ? ++num : num;
mines.contains(QPoint(25+(i-1)*50+(i-1),75+(j-1)*50+(j-1))) ? ++num : num;
mines.contains(QPoint(25+(i+1)*50+(i+1),75+j*50+j)) ? ++num : num;
mines.contains(QPoint(25+(i+1)*50+(i+1),75+(j+1)*50+(j+1))) ? ++num : num;
mines.contains(QPoint(25+(i+1)*50+(i+1),75+(j-1)*50+(j-1))) ? ++num : num;
}
QString path = ":/img/";//空
if (num == 0)
{
path = ":/img/";
}
else
{
path.append(QString::number(num));
path.append(".png");
}
QPixmap pixmap1;
pixmap1.load(path);
painter.drawPixmap(25+i*50+i,75+j*50+j, pixmap1);
}
}
}
//小旗子
if(flag)
{
QPixmap pixmap2;
pixmap2.load(":/img/flag.png");
qDebug() << flagPainter.size();
for (int i=0;i<flagPainter.size();i++)
{
painter.drawPixmap(flagPainter[i], pixmap2);
}
if(isflag)
{
QPixmap pixmap3;
pixmap3.load(":/img/nofan.png");
for (int i=0;i<isflagPainter.size();i++)
{
painter.drawPixmap(isflagPainter[i], pixmap3);
}
}
flag = false;
isflag = false;
}
}
void Widget::boomLocation()
{
//生成坤坤的坐标,重复的重新生成,10个坤坤
for (int i=0;i<10;i++)
{
random_boom_i = QRandomGenerator::global()->bounded(9);
random_boom_j = QRandomGenerator::global()->bounded(9);
//重复了重新选点
while (mines.contains(QPoint(random_boom_i, random_boom_j)))
{
random_boom_i = QRandomGenerator::global()->bounded(9);
random_boom_j = QRandomGenerator::global()->bounded(9);
}
mines.append(QPoint(25+random_boom_i*50+random_boom_i, 75+random_boom_j*50+random_boom_j));
}
}
大体分为四个部分,初始化、鼠标事件、绘画事件、雷生成。其中雷、数字等也可以用QVector<QPoint>的二维数组来存放使用,更简洁一些。
4、资源文件
资源文件放一些小图片,可网上自行找一些扫雷用的工具小图片,也可以下载本文源码,自己找资源文件的小图片。
视频演示
扫雷
总结
本文代码写的比较乱,未优化,作者有点懒,能用就行,写的时候也是缺啥补啥,没有简洁的写,但是这样写应该大部分人能看懂,因为未做优化套层之类的。后续根据需求自己修改即可。主要是为读者提供下思路,功能也为增加太多,后续可根据需求自行添加音频、计时、联网排名等等功能。最后,帮助文档很重要,没事多看看没有坏处,谢谢。