金幣類

金幣這個對象,和金幣翻轉這個特效,其實是可以看作是按鈕和按鈕的特效吧,就和前面的返回按鈕一樣,但這裏需要重新定義金幣類,因為前面封裝的MyPushButton已經不合適了,很多功能在金幣這裏都用不到,而金幣需要的功能又沒有


#ifndef MYCOIN_H
#define MYCOIN_H

#include <QPushButton>

class MyCoin : public QPushButton
{
    Q_OBJECT
public:
    // 參數功能 顯示默認是金幣還是銀幣
    MyCoin(QString CoinImg);

signals:

};

#endif // MYCOIN_H


#include "mycoin.h"
#include<QPixmap>
#include<QMessageBox>
//MyCoin::MyCoin(QWidget *parent) : QPushButton(parent)
//{

//}
MyCoin::MyCoin(QString CoinImg){

    QPixmap pix;
    bool ret = pix.load(CoinImg);
    if(!ret){
        QString str = QString("圖片加載失敗 %1").arg(CoinImg);
        QMessageBox::critical(this,"加載失敗",str);
        return ;
    }
    this->setFixedSize(pix.width(),pix.height());
    this->setStyleSheet("QPushButton{border:0px}");

    this->setIcon(pix);
    this->setIconSize(QSize(pix.width(),pix.height()));
}


封裝好後,就可以在第三個界面playScene.cpp中使用了

// 設置金幣的背景陰影
    for(int i=0;i<4;++i){
        for(int j=0;j<4;++j){
			
			//  ......
			//  ......
			
            // 創建金幣
            MyCoin * coin = new MyCoin(":/res/Coin0001.png");
            coin->setParent(this);
            coin->move(59+i*50,204+j*50);
        }
    }


豐富遊戲中的金幣設定

這裏導入了兩個文件,dataconfig.hdataconfig.cpp,其實就是尤其不同關卡的遊戲設計,哪個位置應該放銀幣哪個位置應該放金幣,不可能隨便這放銀哪放金的吧,萬一是死局不尷尬了?
這裏是用矩陣的形式記錄,共20個矩陣,然後還得記錄對應矩陣的關卡號,所以QMap<int, QVector< QVector<int> > >mData;


在playscene.h中,已經有一個記錄關卡號的成員變量levelIndex了,所以這裏只需要維護一個記錄遊戲數據的4x4的二維數組int gameArray[4][4];


接着初始化

// 初始化遊戲中的二維數組
    dataConfig data;
    for(int i=0;i<4;++i){
        for(int j=0;j<4;++j){
            this->gameArray[i][j] = data.mData[this->levelIndex][i][j];
        }
    }


然後初始化關卡的金幣銀幣,通過int gameArray[4][4];判斷該放金幣還是銀幣

// 設置金幣的背景陰影
    for(int i=0;i<4;++i){
        for(int j=0;j<4;++j){

            QString str;
            if(this->gameArray[i][j]==1){
                str = QString(":/res/Coin0001.png");
            }else{
                str = QString(":/res/Coin0008.png");
            }

            // 創建金幣
            MyCoin * coin = new MyCoin(":/res/Coin0001.png");
            coin->setParent(this);
            coin->move(59+i*50,204+j*50);

        }
    }


擺放好後,就弄翻轉特效了吧?
但你得仔細想想,翻轉特效的功能又需要哪些數據來輔助實現?

  • 金幣翻轉為銀幣,總得還能再翻轉回來吧,這不就得記錄硬幣當前狀態
  • 再記錄下是哪個硬幣,也就是座標
  • 實現翻轉是用8張圖,這裏用一個計時器來輔助實現

所以mycoin.h

public:
    // 參數功能 顯示默認是金幣還是銀幣
    MyCoin(QString CoinImg);

    // 記錄每個硬幣所在座標 
    int posX;
    int posY;
    // 記錄硬幣的狀態
    bool flag;

    // 翻轉特效
    void changeFlag();

    // 正面翻反面 定時器
    QTimer *timer1;
    // 反面翻正面 定時器
    QTimer *timer2;
    // coin圖片1-8
    int min=1;
    int max=8;


初始化座標和狀態,在playscene.cpp創建硬幣時進行

QString str;
            if(this->gameArray[i][j]==1){
                str = QString(":/res/Coin0001.png");
            }else{
                str = QString(":/res/Coin0008.png");
            }

            // 創建金幣
            MyCoin * coin = new MyCoin(str);
            coin->posX = i;
            coin->posY = j;
            coin->flag = this->gameArray[i][j]; // 記錄金幣正反
            coin->setParent(this);
            coin->move(59+i*50,204+j*50);
			
			// 翻轉後還需要修改二維數組
            connect(coin,&MyCoin::clicked,[=](){
               coin->changeFlag();
               gameArray[i][j] = gameArray[i][j] == 0 ? 1 : 0;
            });


翻轉特效

金幣翻轉

mycoin.cpp

// 翻金幣特效
void MyCoin::changeFlag(){

    if(this->flag){

        timer1->start(30) ;
        this->flag = false;
    }
    else
    {
        timer2->start(30);
        this->flag = true;
    }

}
timer1 = new QTimer(this);
    timer2 = new QTimer(this);

    // 監聽定時器
    connect(timer1,&QTimer::timeout,[=](){
       QPixmap pix;
       QString str = QString(":/res/Coin000%1.png").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()));


       // 如果顯示到 最後一張圖   停止定時器
       if(this->min>this->max){
           this->min=1;
           timer1->stop();
       }
    });

    connect(timer2,&QTimer::timeout,[=](){
       QPixmap pix;
       QString str = QString(":/res/Coin000%1.png").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()));


       // 如果顯示到 最後一張圖   停止定時器
       if(this->min>this->max){
           this->max=8;
           timer2->stop();
       }
    });

看着兩個connect,完成了不止一個事件啊,這還能完成循環事件?
這不是藉助了定時器,信號和槽才完成了8個事件嘛


優化特效

如果快速點擊兩次某一個硬幣,其實可以看到,第一次的翻轉1-8並沒有完全做完,就執行8-1了

這裏就優化一下
mycoin.h

// 判斷是否正在執行動畫
    bool isAnimation;
    // 鼠標按下事件重寫
    void mousePressEvent(QMouseEvent* e);

mycoin.cpp

void MyCoin::mousePressEvent(QMouseEvent* e){
    if(this->isAnimation){
        // 正在做動畫 不做鼠標事件處理
        return ;
    }
    else{
        // 交給父類 做默認處理
        QPushButton::mousePressEvent(e);
    }
}


isAnimation什麼時候為true什麼時候為false

沒在做動畫:

  1. 遊戲初始化時沒有做動畫吧,所以初始值為false
  2. 動畫做完還得把標誌設為false吧,也就是定時器停止後

正在做動畫:

  1. 翻金幣特效進行中,就是在做動畫吧,也就是void MyCoin::changeFlag()


// 初始化動畫執行標誌
    isAnimation = false;

	// 定時器
    if(this->min>this->max){
        this->min=1;
        timer1->stop();
        this->isAnimation=false;
    }


	// 翻金幣特效
void MyCoin::changeFlag(){
    if(this->flag){

        timer1->start(30) ;
        this->flag = false;

    }
    else
    {
        timer2->start(30);
        this->flag = true;
    }
    	isAnimation = true;
}


翻動周圍的硬幣,分別在playscene.hplayscene.cpp中進行修改代碼


MyCoin * coinBtn[4][4];


// 將coin放進到維護的金幣二維數組中
            coinBtn[i][j] = coin;

            connect(coin,&MyCoin::clicked,[=](){
               coin->changeFlag();
               gameArray[i][j] = gameArray[i][j] == 0 ? 1 : 0;


               // 延時翻轉周圍的
               QTimer::singleShot(200,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;
                   }
               });

            });


是否勝利

勝利後禁止點擊金幣

//mycoin.h
 bool isWin = false; //是否勝利

//playscene.h
    bool isWin; //是否勝利

//mycoin.cpp
void MyCoin::mousePressEvent(QMouseEvent *e)
{
    if(this->isAnimation || this->isWin)
    {
        qDebug() << "無法點擊";
        return;
    }
    else
    {
        QPushButton::mousePressEvent(e);

    }
}

//playscene.cpp
//判斷是否勝利
                    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)
                    {
                        //勝利了!
                        qDebug() << "遊戲勝利";
                        //將所有按鈕的勝利標誌改為true
                        //如果再次點擊按鈕,直接return,不做響應
                        for(int i = 0 ; i < 4 ; i++)
                        {
                            for(int j = 0 ; j < 4 ; j++)
                            {
                                coinBtn[i][j]->isWin = true;

                            }
                        }
                    }


勝利圖片

//playscene.cpp
	// 創建勝利後彈出的圖片
    QLabel * winLabel = new QLabel(this);
    QPixmap pix ;
    pix.load(":/res/LevelCompletedDialogBg.png");
    winLabel->setGeometry(QRect(0,0,pix.width(),pix.height()));
    winLabel->setPixmap(pix);
    winLabel->move((this->width()-pix.width()) * 0.5 ,   - pix.height() );


// 勝利檢測
   if(this->isWin){
       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(winLabel->x(),winLabel->y(),winLabel->width(),winLabel->height()));
     animation->setEndValue(QRect(winLabel->x(),winLabel->y()+114,winLabel->width(),winLabel->height()));
     animation->setEasingCurve(QEasingCurve::OutBounce);
     animation->start();

                   }


Bug

其實在點完最後一個就能勝利時,再快速點擊其他的硬幣,就會出現勝利的圖片已經彈出來了並且硬幣點擊都禁用了,但第二次點擊的硬幣仍然翻轉了

// 點擊後先禁用所有按鈕
                for(int i=0;i<4;i++)
                {
                    for(int j=0;j<4;j++)
                    {
                       this->coinBtn[i][j]->isWin = true; 
                    }
                }




//等翻完金幣後  將所有按鈕解禁
               for(int i=0;i<4;i++){
                   for(int j=0;j<4;j++){
                       this->coinBtn[i][j]->isWin = false;
                   }
               }


優化代碼

界面切換時,其實會有一個不舒服的地方,就是在第一界面時,將遊戲窗口移動一下,然後點擊開始按鈕跳到第二界面,但第二界面會移動會剛剛的位置,同樣的第二界面進入第三界面一樣有這個問題

chooseScene->hide();
        // 返回時不會使窗口位置變化
        this->setGeometry(chooseScene->geometry());
        this->show();



		// 隱藏第一個界面
        this->hide();
        // 選擇關卡的位置 !!!
        chooseScene->setGeometry(this->geometry());
        // 進入第二個界面
        chooseScene->show();




        //進入到具體的遊戲場景
        playScene = new PlayScene(i+1);
        //將遊戲場景的位置 同步為 上一場景的位置
        playScene->setGeometry(this->geometry());
        playScene->show();


解決方案就是在每一次show之前先將位置設置setGeometry為上一窗口的位置


打包發佈

怎麼將這個遊戲發給別人呢?

直接給.exe是不行的

在Qt中,先切換成release模式再運行一次

android金幣動畫特效_#qt


運行完就在本地項目文件中找到release文件夾下的CoinFlip.exe,將此文件移至桌面的一個空文件夾中,在該空文件夾中,按住shift鍵+鼠標右鍵,會出現在此處打開 PowerShell窗口

打開後輸入命令windeployqt CoinFlip.exe,回車即可

然後這個文件夾就會生成許多庫文件,是連接CoinFlip.exe用的,這樣就可以不需要在什麼編譯環境下就能運行遊戲了

可以將文件夾壓縮發送給別人,也可以上網下載一個軟件HM NIS Edit將文件打包成安裝包