首页 > 技术文章 > QT学习例程1—翻金币教程(教学视频链接:https://www.bilibili.com/video/BV1g4411H78N?p=61)

yongkun 2020-12-06 16:31 原文

1、项目简介

如图所示,将所有的金币都翻转为金色,即可取得胜利。

2、项目基本配置

  • 创建项目(注意不要有中文路径),类名为MainScene
  • 新建Qt Resource File文件,添加图标和音乐等文件到工程项目下

3、主场景

3.1 整个工程项目包括三个场景:

  • 主场景:开始界面(maincene.cpp)
  • 选择关卡场景:进行关卡的选择(chooselevelscene.cpp)
  • 翻金币场景:游戏的主要场景(playscene.cpp)

3.2 在上一步中,我们已经新建了MainScene的类,下面说一下MainScene需要做的工作:

(1)场景的基本配置

  • 设置固定大小(this->setFixedSize(320,588))
  • 设置应用图标(this->setWindowIcon(QPixmap(":/res/Coin0001.png")))
  • 设置窗口标题(this->setWindowTitle("翻金币"))
  • 设置背景图片(需要重写MainScene的PaintEvent事件,记得在头文件声明一下)
void MainScene::paintEvent(QPaintEvent *)
{
    //创建画家,指定绘图设备
    QPainter painter(this);
    //创建QPixmap对象
    QPixmap pix;
    //加载图片
    pix.load(":/res/PlayLevelSceneBg.png");
    //绘制背景图
    painter.drawPixmap(0,0,this->width(),this->height(),pix);

    //加载标题
    pix.load(":/res/Title.png");
    //缩放图片
    pix = pix.scaled(pix.width()*0.5,pix.height()*0.5);
    //绘制标题
    painter.drawPixmap( 10,30,pix.width(),pix.height(),pix);
}

 

(2)创建开始按钮(实现弹跳效果,需要封装出一个按钮控件,来实现这些效果)

  • 新建MyPushButton类,记得修改头文件代码继承于QPushButton
  • 修改MyPushButton的头文件:提供构造的重载版本,可以让MyPushButton提供正常显示的图片及按下后显示的图片;同时需要定义按钮向上和向下跳的特效,需要定义void zoom1( );和void zoom2( );以及需要写一下鼠标按下和释放的事件;
//normalImg 代表正常显示的图片
//pressImg  代表按下后显示的图片,默认为空
MyPushButton(QString normalImg,QString pressImg = "");

QString normalImgPath;  //默认显示图片路径
QString pressedImgPath; //按下后显示图片路径
void zoom1();//向下跳
void zoom2();//向上跳
//重写按下和释放事件
void mousePressEvent(QMouseEvent *);
void mouseReleaseEvent(QMouseEvent *);
  • 编写MyPushButton.cpp的代码(就是在头文件中声明的那几个函数)
MyPushButton::MyPushButton(QString normalImg,QString pressImg)
{
    //成员变量normalImgPath保存正常显示图片路径
    normalImgPath = normalImg;
    //成员变量pressedImgPath保存按下后显示的图片
    pressedImgPath = pressImg;
    //创建QPixmap对象
    QPixmap pixmap;
    //判断是否能够加载正常显示的图片,若不能提示加载失败
    bool ret = pixmap.load(normalImgPath);
    if(!ret)
    {
        qDebug() << normalImg << "加载图片失败!";
    }
    //设置图片的固定尺寸
    this->setFixedSize( pixmap.width(), pixmap.height() );
    //设置不规则图片的样式表
    this->setStyleSheet("QPushButton{border:0px;}");
    //设置图标
    this->setIcon(pixmap);
    //设置图标大小
    this->setIconSize(QSize(pixmap.width(),pixmap.height()));
}

void MyPushButton::zoom1()
{
    //创建动画对象
    QPropertyAnimation * animation1 = new QPropertyAnimation(this, "geometry");
    //设置时间间隔,单位毫秒
    animation1->setDuration(200);
    //创建起始位置
    animation1->setStartValue(QRect(this->x(), this->y(), this->width(), this->height()));
    //创建结束位置
    animation1->setEndValue(QRect(this->x(), this->y(), this->width(), this-> height()));
    //设置缓和曲线,QEasingCurve::OutBounce为弹跳效果
    animation1->setEasingCurve(QEasingCurve::OutBounce);
    //开始执行动画
    animation1->start();
}

void MyPushButton::zoom2()
{

    QPropertyAnimation * animation1 = new QPropertyAnimation(this, "geometry");
    animation1->setDuration(200);

    animation1->setStartValue(QRect(this->x(), this->y()+10, this->width(), this->height()));
    animation1->setStartValue(QRect(this->x(), this->y(), this->width(), this->height()));
    animation1->setEasingCurve(QEasingCurve::OutBounce);
    animation1->start();
}

//鼠标按下事件
void MyPushButton::mousePressEvent(QMouseEvent *e)
{
    if(pressedImgPath != "")//选中路径不为空,显示选中图片
    {
        QPixmap pixmap;
        bool ret = pixmap.load(pressedImgPath);
        if(!ret)
        {
            qDebug()<<pressedImgPath<<"加载图片失败";
        }
        
        this->setFixedSize(pixmap.width(), pixmap.height());
        this->setStyleSheet("QPushButton{border:0px}");
        this->setIcon(pixmap);
        this->setIconSize(QSize(pixmap.width(), pixmap.height()));
    }
    //交给父类执行按下事件
    return QPushButton::mousePressEvent(e);
}

//鼠标释放事件
void MyPushButton::mouseReleaseEvent(QMouseEvent *e)
{
    if(normallImgPath != "")//选中路径不为空,显示选中图片
    {
        QPixmap pixmap;
        bool ret = pixmap.load(normallImgPath);
        if(!ret)
        {
            qDebug()<<normallImgPath<<"加载图片失败";
        }
        this->setFixedSize(pixmap.width(), pixmap.height());
        this->setStyleSheet("QPushButton{border:0px}");
        this->setIcon(pixmap);
        this->setIconSize(QSize(pixmap.width(), pixmap.height()));
    }
    //交给父类执行,释放事件
    return QPushButton::mouseReleaseEvent(e);
}
  • 在MainScene的构造函数中,创建开始按钮,同时监听点击按钮事件,执行特效,进入选择关卡场景
MyPushButton * startBtn = new MyPushButton(":/res/MenuSceneStartButton.png");
startBtn->setParent(this);
startBtn->move(this->width()*0.5-startBtn->width()*0.5,this->height()*0.7);
//监听点击事件,执行特效
connect(startBtn, &MyPushButton::clicked, [=](){
//播放开始的音效资源
startSound->play(); //开始音效
startBtn->zoom1();//向下跳跃
startBtn->zoom2();//向上跳跃
//进入选择关卡场景
//延时0.5秒后,进入选择场景
QTimer::singleShot(1000, this,[=](){
    this->hide();
    chooseScene->show();
   });
});
  • 上述步骤的运行效果如图所示:

4、选择关卡场景

4.1 选择关卡的设置主要包括以下几个方面

  • 场景的基本设置(包括固定的大小、标题之类的)
  • 按钮功能设置(返回按钮和关卡选择按钮)

4.2 详细说一下选择关卡的设置

(1)场景基本设置及背景设置

//设置窗口固定大小
this->setFixedSize(320,588);

//设置图标
this->setWindowIcon(QPixmap(":/res/Coin0001.png"));

//设置标题
this->setWindowTitle("选择关卡");

//创建菜单栏
QMenuBar * bar = this->menuBar();
this->setMenuBar(bar);

//创建开始菜单
QMenu * startMenu = bar->addMenu("开始");

//创建按钮菜单项
QAction * quitAction = startMenu->addAction("退出");

//点击退出 退出游戏
connect(quitAction,&QAction::triggered,[=](){this->close();});

void ChooseLevelScene::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    QPixmap pix;
    pix.load(":/res/OtherSceneBg.png");
    painter.drawPixmap(0,0,this->width(),this->height(),pix);

     //加载标题
    pix.load(":/res/Title.png");
    painter.drawPixmap( (this->width() - pix.width())*0.5,30,pix.width(),pix.height(),pix);
}

(2)返回按钮设置,点击之后返回开始主界面,所有的按钮功能都是调用的MyPushButton中鼠标按下和弹起的功能,由于返回按钮有正常显示图片和点击后显示图片两种模式,所有需要重写MyPushButton.cpp中的MousePressEvent和MouseReleaseEvent(3.2—(2)的那个代码已经是修改过的,可以直接使用),同时音效实现的功能也直接在这里添加了(注意如果要使用音效的话,需要在工程文件代码中第一句加上multimedia,即QT += core gui multimedia,这样就可以在头文件中找到           # include <QSound.h>了)

//返回按钮音效
QSound *backSound = new QSound(":/res/BackButtonSound.wav",this);
//返回按钮
MyPushButton * closeBtn = new MyPushButton(":/res/BackButton.png", ":/res/BackButtonSelected.png");
closeBtn->setParent(this);
closeBtn->move(this->width()-closeBtn->width(), this->height()-closeBtn->height());

//返回按钮功能实现
connect(closeBtn, &MyPushButton::clicked, [=](){
//播放返回音效
    backSound->play();
    QTimer::singleShot(500, this,[=](){
     this->hide();
      //触发自定义信号,关闭自身,该信号写到signals下做声明
     emit this->chooseSceneBack();
        });
});

(3)选择关卡按钮设置(同样加入了音效的功能)

//选择关卡按钮音效
QSound *chooseSound = new QSound(":/res/TapButtonSound.wav",this);
//创建选择关卡按钮
    for(int i = 0; i < 20; i++)
    {
        MyPushButton * menuBtn = new 
        MyPushButton(":/res/LevelIcon.png");
        menuBtn->setParent(this);
        menuBtn->move(130 + (i % 4) * 100, 200 + (i / 4) * 120);

        //监听每个按钮的点击事件
        connect(menuBtn, &MyPushButton::clicked, [=](){
            //播放选择关卡的音效
            chooseSound->play();

            QString str = QString("您选择的是第%1关").arg(i+1);
            qDebug() << str;

            //进入到游戏场景
            this->hide();//将选关场景隐藏掉
            play = new PlayScene(i+1);//创建游戏场景
            play->show();//显示游戏场景

            connect(play, &PlayScene::chooseSceneBack, [=](){
                this->show();
                delete play;
                play = NULL;
            });
        });

        //按钮上显示的文字
        QLabel* label = new QLabel;
        label->setParent(this);
        label->setFixedSize(menuBtn->width(), menuBtn->height());
        label->setText(QString::number(i + 1));
        label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);//设置居中
        label->move(130+ (i % 4) * 100, 300 + (i / 4) * 100);
        label->setAttribute(Qt::WA_TransparentForMouseEvents, true);//鼠标事件穿透
    }
}

 

5、翻金币场景

5.1  翻金币场景的设置主要包括以下几个方面

  • 场景及背景的设置(窗口大小、图标、菜单栏)、背景设置、当前关卡文字显示、金币背景显示
  • 返回按钮设置
  • 创建金币类

5.2  详细的内容介绍如下:

(1)场景及背景的设置(窗口大小、图标、菜单栏)、背景设置、当前关卡文字显示、金币背景显示

  • 头文件声明
public:
    explicit PlayScene(QWidget *parent = nullptr);

    PlayScene(int index);

    //成员变量 记录关卡索引
    int levelIndex;

    //背景函数
    void paintEvent(QPaintEvent *);

    //声明一个成员变量
    int gameArray[4][4];//二维数组数据   

    //金币按钮数组
    MyCoin * coinBtn[4][4];

    //判断是否胜利
    bool isWin;

    QMediaPlayer *endPlayer;

signals:
    void chooseSceneBack();
  • 场景设置
PlayScene::PlayScene(int index)
{
    //qDebug() << "当前关卡为"<< index;
    this->levalIndex = index;
    //设置窗口固定大小
    this->setFixedSize(320,588);
    //设置图标
    this->setWindowIcon(QPixmap(":/res/Coin0001.png"));
    //设置标题
    this->setWindowTitle("翻金币");

    //创建菜单栏
    QMenuBar * bar = this->menuBar();
    this->setMenuBar(bar);
    //创建开始菜单
    QMenu * startMenu = bar->addMenu("开始");
    //创建按钮菜单项
    QAction * quitAction = startMenu->addAction("退出");
    //点击退出 退出游戏
    connect(quitAction,&QAction::triggered,[=](){this->close();});
}
  • 背景设置
void PlayScene::paintEvent(QPaintEvent *)
{
    //加载背景
    QPainter painter(this);
    QPixmap pix;
    pix.load(":/res/PlayLevelSceneBg.png");
    painter.drawPixmap(0,0,this->width(),this->height(),pix);

    //加载标题
    pix.load(":/res/Title.png");
    pix = pix.scaled(pix.width()*0.5,pix.height()*0.5);
    painter.drawPixmap( 10,30,pix.width(),pix.height(),pix);
}
  • 当前关卡显示
//当前关卡标题
    QLabel * label = new QLabel;
    label->setParent(this);
    QFont font;
    font.setFamily("华文新魏");
    font.setPointSize(20);
    label->setFont(font);
    QString str = QString("Leavel: %1").arg(this->levalIndex);
    label->setText(str);
    label->setGeometry(QRect(30, this->height() - 50,120, 50)); //设置大小和位置
  • 创建金币背景图片、金币及相关属性(都是在playscene.c中实现的)
//绘制背景图片
           QPixmap pix=QPixmap(":/res/BoardNode(1).png");
           QLabel *label=new QLabel;
           label->setGeometry(0,0,50,50);
           label->setPixmap(pix);
           label->setParent(this);
//           label->move(57+i*50,200+j*50);
           label->move(150 + i * 80, 300 + j * 80);

//创建金币
           QString str;
           if(this->gameArray[i][j]==1)
           {
               //显示金币
               str=":/res/Coin0001.png";

           }
           else{
               //显示银币
               str=":/res/Coin0008.png";
           }
           MyCoin *coin=new MyCoin(str);
           coin->setParent(this);
//           coin->move(59+i*50,204+j*50);
           coin->move(150 + i * 80, 300 + j * 80);

           //给金币的属性赋值
           coin->posX=i;
           coin->posY=j;
           coin->flag=this->gameArray[i][j]; //  1正面 0反面

           //将金币放入到金币的二维数组里面 以便于后期的维护
           coinBtn[i][j]=coin;

(2)返回按钮设置

    //返回按钮
    MyPushButton * closeBtn = new MyPushButton(":/res/BackButton.png",":/res/BackButtonSelected.png");
    closeBtn->setParent(this);
    closeBtn->move(this->width()-closeBtn->width(),this->height()-closeBtn->height());

    //返回按钮功能实现
    connect(closeBtn,&MyPushButton::clicked,[=](){
        QTimer::singleShot(500, this,[=](){
            this->hide();
            //触发自定义信号,关闭自身,该信号写到 signals下做声明
            emit this->chooseSceneBack();
             }
        );
    });

(3)创建金币类:利用二维数组对金币属性进行维护,且支持点击、翻转特效,把这些功能进行封装

  • 头文件声明
public:
//    explicit MyCoin(QWidget *parent = nullptr);

    //图片路径
    MyCoin(QString btnImg);

    //扩展金币类的属性
    int posX;//x坐标
    int posY;//y坐标
    bool flag;//正反标志

    //改变标志,执行翻转效果
    void changeFlag();
    QTimer * timer1;//正面翻反面 定时器
    QTimer * timer2;//正面翻正面 定时器
    int min = 1;//最小图片
    int max = 8;//最大图片

    //翻转动画的标志
    bool isAnimation = false;

    //重写按钮的按下事件
    void mousePressEvent(QMouseEvent *);
    
    //胜利标志
    bool isWin = false;//胜利标志
  • 构造函数:创建金币对象—提供一个参数—代表传入的是金币还是银币资源,根据路径创建不同的图案
MyCoin::MyCoin(QString butImg)
{

    QPixmap pixmap;
    bool ret = pixmap.load(butImg);
    if(!ret)
    {
        qDebug() << butImg << "加载图片失败!";
    }

    this->setFixedSize( pixmap.width(), pixmap.height() );
    this->setStyleSheet("QPushButton{border:0px;}");
    this->setIcon(pixmap);
    this->setIconSize(QSize(pixmap.width(),pixmap.height()));

}
  • 在当前的工程文件中添加dataconfig.c和dataconfig.h文件
  • 初始化各个关卡
//初始化二维数组
    dataConfig config;
    for(int i = 0 ; i < 4;i++)
    {
        for(int j = 0 ; j < 4; j++)
        {
            gameArray[i][j] = config.mData[this->levalIndex][i][j];
        }
    }
  • 翻金币的特效实现(mycoin.c)
//初始化定时器对象
    time1=new QTimer(this);
    time2=new QTimer(this);

    //监听正面翻反面的信号, 并且翻硬币
    connect(time1,&QTimer::timeout,[=](){
        QPixmap pix;
        QString str=QString(":/res/Coin000%1").arg(this->min++);
        pix.load(str);

        this->setFixedSize(pix.width(),pix.height());
        this->setStyleSheet("QPushButton{border:0px;}");/*设置不规则图片样式*/
        this->setIcon(pix);
        this->setIconSize(QSize(pix.width(),pix.height()));

        //判断如果翻完了 将min 重置为1
        if(this->min>this->max)
        {
            this->min=1;
            isAnimation=false;//停止做动画,禁用按钮(当一个金币在做翻转动作时,另外一个动画不能动)
            time1->stop();
        }
    });

    //监听反面翻正面的信号,并且翻硬币
    connect(time2,&QTimer::timeout,[=](){
        QPixmap pix;
        QString str=QString(":/res/Coin000%1").arg(this->max--);
        pix.load(str);
        this->setFixedSize(pix.width(),pix.height());
        this->setStyleSheet("QPushButton{border:0px;}");/*设置不规则图片样式*/
        this->setIcon(pix);
        this->setIconSize(QSize(pix.width(),pix.height()));

        //判断如果翻完了 将min 重置为1
        if(this->max<this->min)
        {
            this->max=8;
            isAnimation=false;//停止做动画
            time2->stop();
        }
void MyCoin::changeFlag()
{
    //如果是正面 翻成反面
    if(this->flag)
    {
        time1->start(30);
        isAnimation=true;//开始做动画
        this->flag=false;
    }
    else{//反面翻正面
        time2->start(30);
        isAnimation=true;//开始做动画
        this->flag=true;

    }
  •  翻周围硬币(上下左右四个硬币同时翻动,playscene.c代码)
//显示金币背景图案
    for(int i = 0; i < 4; i++)
    {
        for(int j = 0; j < 4; j++)
        {
            //绘制背景图片
            QPixmap pix = QPixmap(":/res/BoardNode.png");
            QLabel *label = new QLabel;
            label->setGeometry(0, 0, pix.width(), pix.height());
            label->setPixmap(pix);
            label->setParent(this);
            label->move(57 + i * 50, 200 + j * 50);

            //初始化金币对象
            QString img;
            if (gameArray[i][j] == 1)
            {
                img = ":/res/Coin0001.png";
            }
            else
            {
                img = ":/res/Coin0008.png";
            }
            MyCoin * coin = new MyCoin(img);
            coin->setParent(this);
            coin->move(150 + i * 80, 300 + j * 80);
            coin->posX = i;//记录x坐标
            coin->posY = j;//记录y坐标
            coin->flag = gameArray[i][j];
            
            //记录每个按钮的位置
            coinBtn[i][j] = coin;

            //测试翻转金币的效果
            connect(coin, &MyCoin::clicked, [=](){
                flipSound->play();
               coin->changeFlag();
               //数组内部记录的标志同步修改
               gameArray[i][j] = gameArray[i][j] == 0 ? 1 : 0;

               //翻转周围金币
               QTimer::singleShot(300, this, [=](){
                  if(coin->posX + 1 <= 3)
                  {
                      coinBtn[coin->posX + 1][coin->posY]->changeFlag();
                      gameArray[coin->posX + 1][coin->posY] =
                      gameArray[coin->posX + 1][coin->posY] == 0 ? 1 : 0;
                  }

                  if(coin->posX - 1 >= 0)
                  {
                      coinBtn[coin->posX - 1][coin->posY] -> changeFlag();
                      gameArray[coin->posX - 1][coin->posY] =
                      gameArray[coin->posX -1][coin->posY] == 0 ? 1 : 0;
                  }

                  if(coin->posY + 1 <= 3)
                  {
                      coinBtn[coin->posX][coin->posY + 1]->changeFlag();
                      gameArray[coin->posX][coin->posY + 1] =
                      gameArray[coin->posX][coin->posY + 1] == 0 ? 1 :0;
                  }

                  if(coin->posY + 1 >= 0)
                  {
                      coinBtn[coin->posX][coin->posY - 1]->changeFlag();
                      gameArray[coin->posX][coin->posY - 1] =
                      gameArray[coin->posX][coin->posY - 1] == 0 ? 1 : 0;
                  }
this->isWin = true;
                  for(int i = 0; i < 4; i++)
                  {
                      for(int j = 0; j < 4; j++)
                      {
                          if(coinBtn[i][j]->flag==false)
                          {
                              this->isWin = false;
                              break;
                          }
                      }
                  }

                  if(this->isWin == true)
                  {
                      winSound->play();
                      qDebug() << "游戏胜利";
                      endPlayer->setMedia(QUrl::fromLocalFile("F:/QT/Day4/CoinFip/res/bkmusic.mp3"));
                      endPlayer->setMedia(QUrl("qrc:/res/bkmusic.mp3"));
                      endPlayer->setVolume(100);
                      endPlayer->play();


                      //禁用所有按钮点击事件
                          for(int i = 0 ; i < 4;i++)
                            {
                              for(int j = 0 ; j < 4; j++)
                              {
                                coinBtn[i][j]->isWin = true;
                              }
                             }

                      //将胜利的图片移动下来
                      QPropertyAnimation *animation=new QPropertyAnimation(winLabel,"geometry");
                      //设置时间间隔
                      animation->setDuration(1000);

                      //设置开始位置
                      animation->setStartValue(QRect(QPoint(winLabel->x(),winLabel->y()),QPoint(winLabel->x()+winLabel->width(),winLabel->height())));
                      //设置结束位置
                      animation->setEndValue(QRect(QPoint(winLabel->x(),winLabel->y()+120),QPoint(winLabel->x()+winLabel->width(),winLabel->height()+120)));

                      //设置缓和曲线
                      animation->setEasingCurve(QEasingCurve::OutBounce);
                      //执行动画
                      animation->start();

                  }

               });
            });
        }
    }
}
  • 胜利图片显示(playscene.c代码)
//胜利图片显示
    QLabel *winLabel = new QLabel;
    QPixmap tmpPix;
    tmpPix.load(":/res/LevelCompletedDialogBg.png");
    winLabel->setParent(this);
    winLabel->setGeometry(0, 0, tmpPix.width(), tmpPix.height());
    winLabel->setPixmap(tmpPix);
    winLabel->move((this->width() - tmpPix.width()) * 0.5, -tmpPix.height());

注意事项:

(1)播放音乐的功能(记得在头文件中声明 #include <QSound>)

 endPlayer=new QMediaPlayer(this);
 endPlayer->setMedia(QUrl::fromLocalFile("F:/QT/Config/res/bkmusic.mp3"));
                      
endPlayer
->setMedia(QUrl("qrc:/res/bkmusic.mp3"));
endPlayer
->setVolume(100);
endPlayer
->play();

(2)金币就类似按钮的类,点击金币就相当于点击按钮(在其头文件中已经把其父类改成了QPushButton)

推荐阅读