1. 程式人生 > 實用技巧 >QT學習例程1—翻金幣教程(教學視訊連結:https://www.bilibili.com/video/BV1g4411H78N?p=61)

QT學習例程1—翻金幣教程(教學視訊連結:https://www.bilibili.com/video/BV1g4411H78N?p=61)

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)