QML小案例 使用QML简单实现翻牌版扫雷游戏(一)

2024-02-23 07:20

本文主要是介绍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,也能识别

值得注意的是:

  1. 在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简单实现翻牌版扫雷游戏(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/737912

相关文章

使用Redis快速实现共享Session登录的详细步骤

《使用Redis快速实现共享Session登录的详细步骤》在Web开发中,Session通常用于存储用户的会话信息,允许用户在多个页面之间保持登录状态,Redis是一个开源的高性能键值数据库,广泛用于... 目录前言实现原理:步骤:使用Redis实现共享Session登录1. 引入Redis依赖2. 配置R

SpringBoot实现RSA+AES自动接口解密的实战指南

《SpringBoot实现RSA+AES自动接口解密的实战指南》在当今数据泄露频发的网络环境中,接口安全已成为开发者不可忽视的核心议题,RSA+AES混合加密方案因其安全性高、性能优越而被广泛采用,本... 目录一、项目依赖与环境准备1.1 Maven依赖配置1.2 密钥生成与配置二、加密工具类实现2.1

使用Python的requests库调用API接口的详细步骤

《使用Python的requests库调用API接口的详细步骤》使用Python的requests库调用API接口是开发中最常用的方式之一,它简化了HTTP请求的处理流程,以下是详细步骤和实战示例,涵... 目录一、准备工作:安装 requests 库二、基本调用流程(以 RESTful API 为例)1.

在Java中实现线程之间的数据共享的几种方式总结

《在Java中实现线程之间的数据共享的几种方式总结》在Java中实现线程间数据共享是并发编程的核心需求,但需要谨慎处理同步问题以避免竞态条件,本文通过代码示例给大家介绍了几种主要实现方式及其最佳实践,... 目录1. 共享变量与同步机制2. 轻量级通信机制3. 线程安全容器4. 线程局部变量(ThreadL

Python38个游戏开发库整理汇总

《Python38个游戏开发库整理汇总》文章介绍了多种Python游戏开发库,涵盖2D/3D游戏开发、多人游戏框架及视觉小说引擎,适合不同需求的开发者入门,强调跨平台支持与易用性,并鼓励读者交流反馈以... 目录PyGameCocos2dPySoyPyOgrepygletPanda3DBlenderFife

使用Python开发一个Ditto剪贴板数据导出工具

《使用Python开发一个Ditto剪贴板数据导出工具》在日常工作中,我们经常需要处理大量的剪贴板数据,下面将介绍如何使用Python的wxPython库开发一个图形化工具,实现从Ditto数据库中读... 目录前言运行结果项目需求分析技术选型核心功能实现1. Ditto数据库结构分析2. 数据库自动定位3

Python yield与yield from的简单使用方式

《Pythonyield与yieldfrom的简单使用方式》生成器通过yield定义,可在处理I/O时暂停执行并返回部分结果,待其他任务完成后继续,yieldfrom用于将一个生成器的值传递给另一... 目录python yield与yield from的使用代码结构总结Python yield与yield

Go语言使用select监听多个channel的示例详解

《Go语言使用select监听多个channel的示例详解》本文将聚焦Go并发中的一个强力工具,select,这篇文章将通过实际案例学习如何优雅地监听多个Channel,实现多任务处理、超时控制和非阻... 目录一、前言:为什么要使用select二、实战目标三、案例代码:监听两个任务结果和超时四、运行示例五

python使用Akshare与Streamlit实现股票估值分析教程(图文代码)

《python使用Akshare与Streamlit实现股票估值分析教程(图文代码)》入职测试中的一道题,要求:从Akshare下载某一个股票近十年的财务报表包括,资产负债表,利润表,现金流量表,保存... 目录一、前言二、核心知识点梳理1、Akshare数据获取2、Pandas数据处理3、Matplotl

分布式锁在Spring Boot应用中的实现过程

《分布式锁在SpringBoot应用中的实现过程》文章介绍在SpringBoot中通过自定义Lock注解、LockAspect切面和RedisLockUtils工具类实现分布式锁,确保多实例并发操作... 目录Lock注解LockASPect切面RedisLockUtils工具类总结在现代微服务架构中,分布