本文主要是介绍QML小案例 使用QML简单实现翻牌版扫雷游戏(一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
乘着最近不忙,学习学习QML的相关知识点,本来打算在项目中直接使用QML开发几个小窗体的,但是才学这玩意,把握不注,所以就打算还是先写几个案例学习学习先。
扫雷这个游戏大家都玩过,功能也简单,正好拿来练手。
分为两章节,当前为C++处理部分
导读
- QML简单介绍
- 扫雷案例
- 获取素材
- 最终效果展示
- 预处理
- C++ 类生成数据
- 定义 元对象宏定义
- 定义 C++类
- 生成一个二维矩阵
- 点击判断点击的是不是炸弹
QML简单介绍
Qt中的QML框架是一种用于描述应用程序用户界面的声明式编程语言,全称是Qt Meta-Object Language(Qt元对象语言)。它允许开发人员和设计人员创建高性能、流畅的动画和具有视觉吸引力的应用程序。
QML主要基于一些可视组件以及这些组件之间的交互、关联来描述用户界面。它是一种高可读性的语言,设计目的是使组件能够以动态方式互连,并且允许在用户界面中轻松地重用和定制组件。Qt QML模块为QML语言开发应用程序和库提供了一个框架,它定义并实现了语言及其引擎架构,并且提供了一个接口,允许应用开发者以自定义类型和集成JavaScript、C++代码的方式来扩展QML语言。
Qt Quick是QML类型和功能的标准库,包含了可视化类型、交互类型、动画、模型和视图、粒子特效和渲染特效等。在QML应用程序中,可以通过一个简单的import语句来使用Qt Quick模块提供的所有功能。Qt Quick模块提供了QML创建用户界面所需的所有基本类型,并允许使用QML语言创建用户界面的QML接口和使用C++语言扩展QML的C++接口。
总的来说,QML框架提供了一种高效的方式来开发用户界面,它结合了声明式编程的灵活性和Qt Quick的强大功能库,使得开发人员能够创建出具有丰富交互和视觉吸引力的应用程序。同时,QML也支持与JavaScript和C++的无缝整合,进一步增强了其开发效率和灵活性。
QML开发资料比较少,也就一些对界面要求比较高的公司会强调需要使用QML开发,我去年看招聘网站上也就三四个公司指定需要掌握QML技术,建议学习的时候多问文言一心或者老北鼻GPT。多了解一些控件使用。
扫雷案例
扫雷这个游戏代码量不高,算法上主要是随机生成一个布设指定数量炸弹的二维数组,循环实现一个9格内的炸弹数量统计和空白处的一个查找临近所有空白的功能。
写案例的时候参考的是蓝色的Win10的扫雷游戏,这个版的扫雷特别经典。敲代码累了就来一局。
获取素材
没找到免费的蓝色版本的扫雷游戏素材,手动截图也不好看,只好另辟蹊径找了一
个卡牌版的素材,大差不差的就将就用着。
素材来源:https://game-icons.net/
最终效果展示
由于素材是单色调的,所有整个窗体就显得很单调。
加载时的特效是随机生成的。炸弹个数限制为40个。
预处理
开发环境:Qt Creator 5.13.1,MinGW 64X
使用 C++类 处理数据 ,使用 QML显示界面
要在QML中使用C++类 ,类必须继承QObject对象,使用QT元对象
C++ 类生成数据
定义 元对象宏定义
声明元对象的通用模版,主要用于可供QML读取和修改C++类中变量声明
#define Attribute_INIT(__type__,__Attr__) \Q_INVOKABLE __type__ get_##__Attr__() const {return this->__Attr__; qDebug()<<"get!";} \Q_INVOKABLE void set_##__Attr__(__type__ _##__Attr__) { this->__Attr__=_##__Attr__; emit __Attr__##_Changed(_##__Attr__);}#define PROPERTY_INIT(__type__,__Attr__) \Q_PROPERTY(__type__ __Attr__ READ get_##__Attr__ WRITE set_##__Attr__( NOTIFY __Attr__##_Changed))#define Signals_INIT(__type__,__Attr__) \void __Attr__##_Changed(__type__);
定义 C++类
定义一个C++操作类 Operate_Connector,用来保存炸弹数量,地图宽带,地图高度,炸弹二维矩阵等参数。
Operate_Connector头文件
///操作类
class Operate_Connector:public QObject
{Q_OBJECTPROPERTY_INIT(int,Bomb_Count)PROPERTY_INIT(int,Plat_Rows)PROPERTY_INIT(int,Plat_Cols)PROPERTY_INIT(int,animType)Q_PROPERTY(QVector<QVector<int>> MapBomb MEMBER MapBomb)public:///必须实现QObject(parent) 否则无法设置idQ_INVOKABLE Operate_Connector(QObject *parent = nullptr);// Q_INVOKABLE int GetBombCount() {return this->Bomb_Count;}// Q_INVOKABLE int SetBombCount(int _count) {this->Bomb_Count=_count;}Attribute_INIT(int,Bomb_Count)Attribute_INIT(int,Plat_Rows)Attribute_INIT(int,Plat_Cols)Attribute_INIT(int,animType)///生成一个 int类型 二维矩阵Q_INVOKABLE void reconstruction();///是否按住炸弹Q_INVOKABLE int is_Bomb(int index);//加载时的动画特效enum animEffects{//随机random=0,//从左到右leftToright=1,//从中心向左右散开centerToSide=2,//从中心向左右散开centerToSpread=3};//Roster枚举类型注册Q_ENUM(animEffects);private:///炸弹数量int Bomb_Count ;///地图宽带int Plat_Rows;///地图高度int Plat_Cols;///加载时的动画特效int animType;///二维矩阵/// -1 炸弹/// 0 空白/// 1-8 九格之内炸弹数量QVector<QVector<int>> MapBomb;/// 初始化全为 -1/// 当统计-1 个数等于炸弹个数时 胜利QVector<int> VectBomb;///列排序基准点static int datum_mark;/// 列排序/// 适用于中心散开特效static bool sortType(QPair<int,int> a1,QPair<int,int> a2);///查找空白项void findblank(QPair<int,int> item, QList<QPair<int,int>> &blankitems);///验证炸弹数量void verify_bomb();
public://!信号
signals:Signals_INIT(int,Bomb_Count);Signals_INIT(int,Plat_Rows);Signals_INIT(int,Plat_Cols);Signals_INIT(int,animType);//!显示牌Q_INVOKABLE void blank_clearing(int index,int board,int duration) const;//!游戏胜利void gameWin();//!游戏失败void gameFailure();
};
生成一个二维矩阵
初始化二维矩阵QVector<QVector<int>> MapBomb;
数据
使用 #include <QRandomGenerator>
生成随机数
并随机插入指定Bomb_Count
个数的炸弹,
再循环统计每个单元格9格范围内的炸弹数量
void Operate_Connector::reconstruction()
{if(Bomb_Count==0||Plat_Rows==0||Plat_Cols==0)return;datum_mark=ceil(Plat_Cols/2);MapBomb=QVector<QVector<int>>(Plat_Rows, QVector<int>(Plat_Cols, 0));VectBomb=QVector<int>(Plat_Cols*Plat_Rows, -1);//加载特效animType= QRandomGenerator::global()->bounded(4);//随机插入炸弹for(int B=0;B<Bomb_Count;B++){do{// 生成一个介于0(包含)和行数/列数(不包含)之间的随机整数int r_row = QRandomGenerator::global()->bounded(Plat_Rows);int r_col = QRandomGenerator::global()->bounded(Plat_Cols);if(MapBomb[r_row][r_col]!=-1){MapBomb[r_row][r_col]=-1;break;}}while (true);}//统计当前项3*3矩形范围内的炸弹个数for(int r=0;r<Plat_Rows;r++){for(int c=0;c<Plat_Cols;c++){//炸弹不统计if(MapBomb[r][c]==-1)continue;int number=0;//左上if((r-1<Plat_Rows && r-1 >=0 ) &&(c-1<Plat_Cols && c-1 >=0 )){if(MapBomb[r-1][c-1]==-1)number++;}//上if((r-1<Plat_Rows && r-1 >=0 ) &&(c<Plat_Cols && c >=0 )){if(MapBomb[r-1][c]==-1)number++;}//右上if((r-1<Plat_Rows && r-1 >=0 ) &&(c+1<Plat_Cols && c+1 >=0 )){if(MapBomb[r-1][c+1]==-1)number++;}//右if((r<Plat_Rows && r >=0 ) &&(c+1<Plat_Cols && c+1 >=0 )){if(MapBomb[r][c+1]==-1)number++;}//右下if((r+1<Plat_Rows && r+1 >=0 ) &&(c+1<Plat_Cols && c+1 >=0 )){if(MapBomb[r+1][c+1]==-1)number++;}//下if((r+1<Plat_Rows && r+1 >=0 ) &&(c<Plat_Cols && c >=0 )){if(MapBomb[r+1][c]==-1)number++;}//左下if((r+1<Plat_Rows && r+1 >=0 ) &&(c-1<Plat_Cols && c-1 >=0 )){if(MapBomb[r+1][c-1]==-1)number++;}//左if((r<Plat_Rows && r >=0 ) &&(c-1<Plat_Cols && c-1 >=0 )){if(MapBomb[r][c-1]==-1)number++;}MapBomb[r][c]=number;}}
}
点击判断点击的是不是炸弹
判断点击索引处是炸弹还是空白还是概率值
如果是空白则展开所有空白项,如果是概率就翻转自己。
炸弹游戏结束
void Operate_Connector::findblank(QPair<int,int> item, QList<QPair<int,int>> &blankitems)
{int r=item.first;int c=item.second;//左上if((r-1<Plat_Rows && r-1 >=0 ) &&(c-1<Plat_Cols && c-1 >=0 ) &&MapBomb[r-1][c-1]!=-1){if(!blankitems.contains(QPair<int,int>(r-1,c-1))){blankitems.append(QPair<int,int>(r-1,c-1));if(MapBomb[r-1][c-1]==0)findblank(QPair<int,int>(r-1,c-1),blankitems);}}//上if((r-1<Plat_Rows && r-1 >=0 ) &&(c<Plat_Cols && c >=0 ) &&MapBomb[r-1][c]!=-1){if(!blankitems.contains(QPair<int,int>(r-1,c))){blankitems.append(QPair<int,int>(r-1,c));if(MapBomb[r-1][c]==0)findblank(QPair<int,int>(r-1,c),blankitems);}}//右上if((r-1<Plat_Rows && r-1 >=0 ) &&(c+1<Plat_Cols && c+1 >=0 ) &&MapBomb[r-1][c+1]!=-1){if(!blankitems.contains(QPair<int,int>(r-1,c+1))){blankitems.append(QPair<int,int>(r-1,c+1));if(MapBomb[r-1][c+1]==0)findblank(QPair<int,int>(r-1,c+1),blankitems);}}//右if((r<Plat_Rows && r >=0 ) &&(c+1<Plat_Cols && c+1 >=0 &&MapBomb[r][c+1]!=-1)){if(!blankitems.contains(QPair<int,int>(r,c+1))){blankitems.append(QPair<int,int>(r,c+1));if(MapBomb[r][c+1]==0)findblank(QPair<int,int>(r,c+1),blankitems);}}//右下if((r+1<Plat_Rows && r+1 >=0 ) &&(c+1<Plat_Cols && c+1 >=0 &&MapBomb[r+1][c+1]!=-1)){if(!blankitems.contains(QPair<int,int>(r+1,c+1))){blankitems.append(QPair<int,int>(r+1,c+1));if(MapBomb[r+1][c+1]==0)findblank(QPair<int,int>(r+1,c+1),blankitems);}}//下if((r+1<Plat_Rows && r+1 >=0 ) &&(c<Plat_Cols && c >=0 ) &&MapBomb[r+1][c]!=-1){if(!blankitems.contains(QPair<int,int>(r+1,c))){blankitems.append(QPair<int,int>(r+1,c));if(MapBomb[r+1][c]==0)findblank(QPair<int,int>(r+1,c),blankitems);}}//左下if((r+1<Plat_Rows && r+1 >=0 ) &&(c-1<Plat_Cols && c-1 >=0 ) &&MapBomb[r+1][c-1]!=-1){if(!blankitems.contains(QPair<int,int>(r+1,c-1))){blankitems.append(QPair<int,int>(r+1,c-1));if(MapBomb[r+1][c-1]==0)findblank(QPair<int,int>(r+1,c-1),blankitems);}}//左if((r<Plat_Rows && r >=0 ) &&(c-1<Plat_Cols && c-1 >=0 ) &&MapBomb[r][c-1]!=-1){if(!blankitems.contains(QPair<int,int>(r,c-1))){blankitems.append(QPair<int,int>(r,c-1));if(MapBomb[r][c-1]==0)findblank(QPair<int,int>(r,c-1),blankitems);}}}bool Operate_Connector::sortType(QPair<int,int> a1,QPair<int,int> a2)
{return abs(datum_mark-a1.second)>abs(datum_mark-a2.second);
}//验证是否已经打开了所有项
void Operate_Connector::verify_bomb()
{int _BombCount=0;for(int i=0;i<VectBomb.count();i++){if(VectBomb[i]==-1)_BombCount++;}if(_BombCount==Bomb_Count)emit gameWin();
}#include <QPair>
int Operate_Connector::is_Bomb(int index)
{//QML的ceil与Qt的Ceil不一样int row= ceil(index/Plat_Rows);int col=index-row*Plat_Cols;if(row>=Plat_Rows || col>=Plat_Cols)return -1;//默认中心散开特效 centerSpreadQList<QPair<int,int>> blankitems;if(MapBomb[row][col]==-1)goto gameFailure;blankitems.append(QPair<int,int>(row,col));if(MapBomb[row][col]==0){datum_mark=col;//先找到空白附件的所有空白项findblank(QPair<int,int>(row,col), blankitems);//按照列间隔排序 可以不用排序qSort(blankitems.begin(),blankitems.end(),sortType);}//计算时长for(int s=0;s<blankitems.count();s++){int index=blankitems[s].first*Plat_Rows+blankitems[s].second;int board=MapBomb[blankitems[s].first][blankitems[s].second];VectBomb[index]=board;int duration=ceil(((double)abs(blankitems[s].second-col)/Plat_Cols)*1000)+200;emit blank_clearing(index,board,duration);}verify_bomb();// qDebug()<<"[Row] : "<<row<<" [Col] : "<<col;// emit blank_clearing(index,MapBomb[row][col],200);return MapBomb[row][col];gameFailure://按住炸弹 全展开for(int r=0;r<Plat_Rows;r++){for(int c=0;c<Plat_Cols;c++){blankitems.append(QPair<int,int>(r,c));}}qSort(blankitems.begin(),blankitems.end(),sortType);//计算时长for(int s=0;s<blankitems.count();s++){int index=blankitems[s].first*Plat_Rows+blankitems[s].second;int board=MapBomb[blankitems[s].first][blankitems[s].second];VectBomb[index]=board;int duration=ceil(((double)abs(blankitems[s].second-col)/Plat_Cols)*1000)+200;qDebug()<<duration;emit blank_clearing(index,board,duration);}emit gameFailure();return MapBomb[row][col];}
C++类中主要涉及到元对象的使用:
Q_INVOKABLE
用于注册方法,
Q_PROPERTY
用于注册变量
Q_ENUM
用于注册枚举
同时需要再main文件中注册实例
qmlRegisterType<Operate_Connector>("DAL_Connector", 1, 0, "Operate_Connector");
这样就能在QML文件中内直接调用C++类
主要信号和槽函数前面可以不加Q_INVOKABLE,也能识别
值得注意的是:
- 在QML文件中使用
Connections
链接C++类信号时
必须on+首字母大写函数名,括号内可直接使用信号参数
如上例
Connections
{
onBlank_clearing
: {
// console.log("index : ",index
) // 当C++信号被发射时,这里将被调用
// console.log("board : ",board
)
// console.log("duration : ",duration
)
}
onGameWin
: { }
onGameFailure
:{ }
}2.C++中 ceil(向下取整) 可以取到0,
0为整数
而QML中Math.ceil(向下取整)只取值到1,0不为整数
这篇关于QML小案例 使用QML简单实现翻牌版扫雷游戏(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!