乘着最近不忙,学习学习QML的相关知识点,本来打算在项目中直接使用QML开发几个小窗体的,但是才学这玩意,把握不注,所以就打算还是先写几个案例学习学习先。
扫雷这个游戏大家都玩过,功能也简单,正好拿来练手。
分为两章节,当前为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_OBJECT
    PROPERTY_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) 否则无法设置id
    Q_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;

    //默认中心散开特效 centerSpread
    QList<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:{ }
    }

QML实现界面篇 : QML小案例 使用QML简单实现翻牌版扫雷游戏(二)

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐