金幣類
金幣這個對象,和金幣翻轉這個特效,其實是可以看作是按鈕和按鈕的特效吧,就和前面的返回按鈕一樣,但這裏需要重新定義金幣類,因為前面封裝的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.h和dataconfig.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
沒在做動畫:
- 遊戲初始化時沒有做動畫吧,所以初始值為
false - 動畫做完還得把標誌設為
false吧,也就是定時器停止後
正在做動畫:
- 翻金幣特效進行中,就是在做動畫吧,也就是
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.h,playscene.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模式再運行一次
運行完就在本地項目文件中找到release文件夾下的CoinFlip.exe,將此文件移至桌面的一個空文件夾中,在該空文件夾中,按住shift鍵+鼠標右鍵,會出現在此處打開 PowerShell窗口
打開後輸入命令windeployqt CoinFlip.exe,回車即可
然後這個文件夾就會生成許多庫文件,是連接CoinFlip.exe用的,這樣就可以不需要在什麼編譯環境下就能運行遊戲了
可以將文件夾壓縮發送給別人,也可以上網下載一個軟件HM NIS Edit將文件打包成安裝包