QT实战 之翻金币游戏
相较于原版的优化:
游戏界面截图
游戏文件目录
源代码如下所示
FilpCoins.pro
应当添加 CONFIG += c++11 以便支持C++11的Lambda表达式;
应当添加QT += multimedia 以便支持音效的添加;
#------------------------------------------------- # # Project created by QtCreator 2023-11-23T12:08:33 # #------------------------------------------------- QT += core gui multimedia greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = FilpCoins TEMPLATE = app SOURCES += main.cpp\ mainwindow.cpp \ mybutton.cpp \ levelscene.cpp \ gamescene.cpp \ mycoin.cpp HEADERS += mainwindow.h \ mybutton.h \ levelscene.h \ gamescene.h \ mycoin.h FORMS += mainwindow.ui RESOURCES += \ res.qrc CONFIG += c++11
main.cpp 系统默认生成
#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
mainwindow.h主窗口头文件
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); void paintEvent(QPaintEvent *);//绘制背景图片 private: Ui::MainWindow *ui; }; #endif // MAINWINDOW_H
mainwindow.cpp主窗口源文件
#include "mainwindow.h" #include "ui_mainwindow.h" #include <QMenu> #include <QPainter> #include "mybutton.h" #include <QDebug> #include <QPushButton> #include <QTimer> #include "levelscene.h" #include <QSound> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); //设置窗体标题 setWindowTitle("翻金币"); //设置窗体固定尺寸 setFixedSize(390,570); //设置窗体图标 setWindowIcon(QIcon(QPixmap(":/res/Coin0001.png"))); //添加开始菜单 QMenu* startMenu=new QMenu("开始"); QAction* quitAction= startMenu->addAction("退出"); connect(quitAction,&QAction::triggered,this,[=](){ this->close(); }); ui->menuBar->addMenu(startMenu); //定义开始按钮 MyButton* startBtn=new MyButton(":/res/MenuSceneStartButton.png"); startBtn->setParent(this); startBtn->move(this->width()*0.5-startBtn->width()*0.5,this->height()*0.5); //开始按钮的音效 QSound* startSound=new QSound(":/res/start.wav"); LevelScene* levelScene=new LevelScene(); //连接开始按钮点击事件和槽函数 connect(startBtn,&MyButton::clicked,startBtn,[=](){ //播放音效 startSound->play(); //跳跃动画 startBtn->JumpUpAndDown(); //延迟打开新窗口(主要是为了显示动画和播放音效) QTimer::singleShot(1000,[=](){ levelScene->setGeometry(this->geometry()); levelScene->show(); this->hide(); }); }); //接收到下级窗口的退回信号 connect(levelScene,&LevelScene::backSignal,[=](){ QTimer::singleShot(200,[=](){ this->setGeometry(levelScene->geometry()); this->show(); levelScene->hide(); }); }); } //析构函数 MainWindow::~MainWindow() { delete ui; } //绘制背景图片 void MainWindow:: paintEvent(QPaintEvent *) { QPixmap pix= QPixmap(":/res/OtherSceneBg.png"); QPainter* painter=new QPainter(this); painter->drawPixmap(0,0,pix); QPixmap pix2=QPixmap(":res/Title.png"); pix2.scaled(pix2.width()*0.5,pix2.height()*0.5); painter->drawPixmap(10,30,pix2); }
levelscene.h选择关卡窗口头文件
#ifndef LEVELSCENE_H #define LEVELSCENE_H #include <QMainWindow> class LevelScene : public QMainWindow { Q_OBJECT private: public: explicit LevelScene(QWidget *parent = 0);//构造函数 void paintEvent(QPaintEvent *);//重写绘制函数,以便显示背景图片 signals: void backSignal();//点击返回按钮,激活返回信号,以便上级窗口进行处理 public slots: }; #endif // LEVELSCENE_H
levelscene.cpp选择关卡窗体源文件
#include "levelscene.h" #include <QPixmap> #include <QPainter> #include <QMenu> #include <QMenuBar> #include "mybutton.h" #include <QLabel> #include "gamescene.h" #include <QTimer> #include <QSound> LevelScene::LevelScene(QWidget *parent) : QMainWindow(parent) { //设置窗体标题 setWindowTitle("选择关卡"); //设置窗体固定尺寸 setFixedSize(390,570); //设置窗体图标 setWindowIcon(QIcon(QPixmap(":/res/Coin0001.png"))); //添加开始菜单 QMenu* startMenu=new QMenu("开始"); QAction* quitAction= startMenu->addAction("退出"); connect(quitAction,&QAction::triggered,this,[=](){ this->close(); }); QMenuBar* menuBar=new QMenuBar(); menuBar->addMenu(startMenu); setMenuBar(menuBar); //添加返回按钮 MyButton* backBtn=new MyButton(":/res/BackButton.png",":/res/BackButtonSelected.png"); backBtn->setParent(this); backBtn->move(this->width()-backBtn->width(),this->height()-backBtn->height()); //返回按钮音效 QSound* sound=new QSound(":/res/back.wav"); //连接返回按钮和对应的槽函数 connect(backBtn,&MyButton::clicked,[=](){ sound->play(); emit backSignal(); }); //设置20个关卡按钮 QPixmap levelPix=QPixmap(":/res/LevelIcon.png"); QFont font= QFont(); font.setFamily("华文琥珀"); font.setPixelSize(25); //选关按钮音效 QSound* selectSound=new QSound(":/res/select.wav"); //生成20个关卡按钮 for(int i=0;i<20;i++) { MyButton* btn=new MyButton(":/res/LevelIcon.png"); btn->setParent(this); btn->move(50+i%4*75,90+i/4*80); QLabel *label=new QLabel(btn); label->setText(QString::number(i+1)); label->setGeometry(0,0,btn->width(),btn->height()); label->setAlignment(Qt::AlignCenter); label->setFont(font); //连接关卡按钮点击信号和对应的槽函数 connect(btn,&MyButton::clicked,btn,[=](){ selectSound->play(); btn->JumpUpAndDown(); //延迟生成新窗口 QTimer::singleShot(1000,[=](){ GameScene *gameScene=new GameScene(i+1); gameScene->setGeometry(this->geometry()); gameScene->show(); this->hide(); connect(gameScene,&GameScene::backSignal,[=](){ QTimer::singleShot(200,[=](){ this->setGeometry(gameScene->geometry()); this->show(); gameScene->close(); }); }); }); }); } } //重写绘制函数,以绘制背景图片 void LevelScene::paintEvent(QPaintEvent *) { QPixmap pix=QPixmap(":/res/MenuSceneBg.png"); QPainter* painter=new QPainter(this); painter->drawPixmap(0,0,pix); }
gamescene.h游戏窗口头文件
#ifndef GAMESCENE_H #define GAMESCENE_H #include "mybutton.h" #include <QWidget> #include "mycoin.h" class GameScene : public QWidget { Q_OBJECT private: QVector<QVector<int>> GetLevelData(int level); QVector<QVector<int>> data;//当前关卡数据 QVector<QVector<MyCoin*>>coins;//所有金币按钮 QVector<QPoint> answer;//答案数据 int start;//一个索引值 MyButton* restartBtn;//重新开始按钮 MyButton* answerBtn;//显示答案按钮 public: explicit GameScene(int level);//构造函数 void paintEvent(QPaintEvent *);//重写绘制函数,以绘制背景图片 int level;//当前关卡等级 bool IsSucceed();//判断当前关卡是否通过的函数 void FlipNeighbours(MyCoin*);//翻转所有邻居金币 void EnableCoins(bool);//启用或禁用所有金币按钮 void ReStart();//重新开始函数 void ShowAnswer();//显示答案函数 signals: void backSignal();//返回按钮触发的信号函数 public slots: }; #endif // GAMESCENE_H
gamescene.cpp游戏窗口源文件
#include "gamescene.h" #include <QIcon> #include <QMenuBar> #include <QPixmap> #include <QLabel> #include <QFont> #include <QPainter> #include <QVector> #include <QTimer> #include <QDebug> #include <QPropertyAnimation> #include <QStack> #include <QSound> //游戏场景类 GameScene::GameScene(int level) : QWidget() { this->level=level; //设置窗体标题 setWindowTitle("开始游戏"); //设置窗体固定尺寸 setFixedSize(390,570); //设置窗体图标 setWindowIcon(QIcon(QPixmap(":/res/Coin0001.png"))); //添加返回按钮 MyButton* backBtn=new MyButton(":/res/BackButton.png",":/res/BackButtonSelected.png"); backBtn->setParent(this); QSound* backSound=new QSound(":/res/back.wav"); backBtn->move(this->width()-backBtn->width(),this->height()-backBtn->height()); connect(backBtn,&MyButton::clicked,backBtn,[=](){ backSound->play(); emit backSignal(); }); //添加重新开始按钮 restartBtn=new MyButton(":/res/RestartButton.png",":/res/RestartButtonSelected.png"); restartBtn->setParent(this); restartBtn->move(0,this->height()-backBtn->height()); //生成音效对象 QSound* restartSound=new QSound(":/res/restart.wav"); connect(restartBtn,&MyButton::clicked,backBtn,[=](){ //播放音效 restartSound->play(); //禁用两个按钮 restartBtn->setEnabled(false); answerBtn->setEnabled(false); //先禁用所有金币按钮 EnableCoins(false); ReStart(); QTimer::singleShot(1000,[=](){ //启用所有金币按钮 EnableCoins(true); //再启用两个按钮 restartBtn->setEnabled(true); answerBtn->setEnabled(true); }); }); //添加显示答案按钮 answerBtn=new MyButton(":/res/AnswerButton.png",":/res/AnswerButtonSelected.png"); answerBtn->setParent(this); answerBtn->move(this->width()*0.5-backBtn->width()*0.5,this->height()-backBtn->height()); //生成音效对象 QSound* answerSound=new QSound(":/res/answer.wav"); connect(answerBtn,&MyButton::clicked,backBtn,[=](){ //播放按钮音效 answerSound->play(); //禁用两个按钮 restartBtn->setEnabled(false); answerBtn->setEnabled(false); //先禁用所有金币按钮 EnableCoins(false); ShowAnswer(); QTimer::singleShot(1000*(answer.length()+1),[=](){ //再启用两个按钮 restartBtn->setEnabled(true); answerBtn->setEnabled(true); }); }); //显示当前关卡的标签 QLabel* levelLabel=new QLabel(this); levelLabel->setGeometry(30,20,300,40); QFont font= QFont(); font.setFamily("华文隶书"); font.setPixelSize(40); font.setBold(true); font.setItalic(true); levelLabel->setFont(font); QString text=QString("Level: %1").arg(level); levelLabel->setText(text); //创建成功标签 QPixmap sucPix=QPixmap(":/res/LevelCompletedDialogBg.png"); QLabel* sucLabel=new QLabel(this); sucLabel->setGeometry(this->width()*0.5-sucPix.width()*0.5,-sucPix.height(),sucPix.width(),sucPix.height()); sucLabel->setPixmap(sucPix); //显示金币背景图,金币按钮 data= GetLevelData(level); QPixmap coinBack=QPixmap(":/res/BoardNode.png"); //用于管理所有的金币按钮 coins=QVector<QVector<MyCoin*>>(); //翻转音效对象 QSound* flipSound=new QSound(":/res/flip.wav"); //成功音效对象 QSound* succeedSound=new QSound(":/res/succeed.wav"); for(int i=0;i<5;i++) { //一行金币按钮的容器 QVector<MyCoin*>coinLine; for(int j=0;j<5;j++) { //设置金币按钮的背景图片 QLabel* label=new QLabel(this); label->setGeometry(i*coinBack.width()+75,j*coinBack.height()+150,coinBack.width(),coinBack.height()); label->setPixmap(coinBack); //设置金币按钮,并为其属性赋值 MyCoin* coin=new MyCoin(data[i][j],label); coin->move(2,3); coinLine.push_back(coin); coin->rowIndex=i; coin->colIndex=j; //为每个金币绑定点击翻转事件(同时翻转周边金币) connect(coin,&MyCoin::clicked,[=](){ //翻转音效 flipSound->play(); //禁用所有金币按钮 EnableCoins(false); //翻转当前金币 coin->Flip(); //邻居金币 延迟翻转 QTimer::singleShot(200,[=](){ FlipNeighbours(coin); //每次翻转完成,都判定是否成功 QTimer::singleShot(1000,[=](){ if(IsSucceed()) { //播放成功音效 succeedSound->play(); //为成功标签 设置动画 QPropertyAnimation* anim=new QPropertyAnimation(sucLabel,"geometry"); anim->setDuration(1000); anim->setStartValue(QRect(sucLabel->x(),sucLabel->y(),sucLabel->width(),sucLabel->height())); anim->setEndValue(QRect(sucLabel->x(),sucLabel->y()+sucLabel->height()+30,sucLabel->width(),sucLabel->height())); anim->setEasingCurve(QEasingCurve::OutBounce); anim->start(); //禁用所有金币按钮 EnableCoins(false); //禁用两个按钮 restartBtn->setEnabled(false); answerBtn->setEnabled(false); } else { //如果没有成功,则启用所有金币按钮,以便进行下一步翻转 EnableCoins(true); } }); }); }); } //将一行金币按钮添加到金币按钮容器中 coins.push_back(coinLine); } } //绘制背景图片 void GameScene::paintEvent(QPaintEvent *) { QPixmap pix=QPixmap(":/res/PlayLevelSceneBg.png"); QPainter* painter=new QPainter(this); painter->drawPixmap(0,0,pix); } //随机生成关卡数据 QVector<QVector<int>> GameScene:: GetLevelData(int level) { data=QVector<QVector<int>>(); answer=QVector<QPoint>(); //数据初始化,全为1(金币) for(int i=0;i<5;i++) { QVector<int>line; for(int j=0;j<5;j++) { line.push_back(1); } data.push_back(line); } //按照级别,随机反向翻转level次,生成当前关卡的数据 int row=-1,col=-1,tempRow,tempCol; for(int n=0;n<level;n++) { tempRow= rand()%5;//生成行随机数 tempCol=rand()%5;//生成列随机数 while (row==tempRow&&col==tempCol) { tempRow= rand()%5;//生成行随机数 tempCol=rand()%5;//生成列随机数 } row=tempRow; col=tempCol; answer.push_front(QPoint(row,col)); for(int i=0;i<5;i++) { for(int j=0;j<5;j++) { //翻转自身 data[row][col]=1-data[row][col]; //翻转上下左右的邻居 if(row+1<5) { data[row+1][col]=1-data[row+1][col]; } if(row-1>=0) { data[row-1][col]=1-data[row-1][col]; } if(col+1<5) { data[row][col+1]=1-data[row][col+1]; } if(col-1>=0) { data[row][col-1]=1-data[row][col-1]; } } } } return data; } //判定是否全部翻转成金币(当前关卡通过条件) bool GameScene::IsSucceed() { for(int i=0;i<5;i++) { for(int j=0;j<5;j++) { if(!coins[i][j]->isGold) { return false; } } } return true; } //翻转邻居金币(上下左右) void GameScene::FlipNeighbours(MyCoin *coin) { if(coin->rowIndex+1<5) { coins[coin->rowIndex+1][coin->colIndex]->Flip(); } if(coin->rowIndex-1>=0) { coins[coin->rowIndex-1][coin->colIndex]->Flip(); } if(coin->colIndex+1<5) { coins[coin->rowIndex][coin->colIndex+1]->Flip(); } if(coin->colIndex-1>=0) { coins[coin->rowIndex][coin->colIndex-1]->Flip(); } } //启用或禁用所有金币按钮 void GameScene::EnableCoins(bool enable) { for(int i=0;i<5;i++) { for(int j=0;j<5;j++) { //通过设置鼠标穿透属性,来禁用或启用金币按钮 //如果用QPushButton::setEnabled()函数,当禁用按钮时,按钮会灰显,这不是我们想要的效果 coins[i][j]->setAttribute(Qt::WA_TransparentForMouseEvents,!enable); } } } //重新开始,将显示的金币矩阵恢复到初始状态 void GameScene::ReStart() { for(int i=0;i<5;i++) { for(int j=0;j<5;j++) { coins[i][j]->ChangeIcon(data[i][j]); } } } //重置金币并逐步显示答案 void GameScene::ShowAnswer() { //先重置关卡数据 ReStart(); //按步骤显示解答顺序 QTimer* timer=new QTimer(); timer->setInterval(1000); start=0; connect(timer,&QTimer::timeout,[=](){ MyCoin* coin= coins[answer[start].x()] [answer[start].y()]; start++; if(start>=answer.length()) { timer->stop(); } coin->Flip(); QTimer::singleShot(200,[=](){ FlipNeighbours(coin); }); }); timer->start(); }
mybutton.h开始游戏按钮和返回按钮等
#ifndef MYBUTTON_H #define MYBUTTON_H #include <QWidget> #include <QPushButton> #include <QString> class MyButton : public QPushButton { Q_OBJECT private: QString img1;//第一张图片路径 QString img2;//第二张图片路径 QPixmap pix1;//第一张图片对象 QPixmap pix2;//第二张图片对象 bool hasPix2;//是否有两张背景图 public: explicit MyButton(QString imgPath1,QString imgPath2=""); void JumpUpOrDown(int,QEasingCurve);//向上或者向下跳,向下传入正数,反之负数 void JumpUpAndDown();//先向上跳在向下跳 const int jumpTime=300;//跳跃持续事件 void mousePressEvent(QMouseEvent *e);//适用于两张背景图片的切换(如有) void mouseReleaseEvent(QMouseEvent *e);//适用于两张背景图片的切换(如有) signals: public slots: }; #endif // MYBUTTON_H
mybutton.cpp开始游戏按钮和返回按钮等
#include "mybutton.h" #include <QDebug> #include <QPropertyAnimation> #include <QTimer> MyButton::MyButton(QString img1,QString img2) { this->img1=img1; this->img2=img2; pix1=QPixmap(img1); if(!pix1.load(img1)) { qDebug()<<"图片加载失败:"<<img1; return; } setFixedSize(pix1.width(),pix1.height()); setIcon(pix1); setIconSize(QSize(pix1.width(),pix1.height())); setStyleSheet("QPushButton{border:0px;}"); pix2=QPixmap(); hasPix2=pix2.load(img2); } //先向上跳,再向下跳 void MyButton::JumpUpAndDown() { JumpUpOrDown(-this->height()*0.5,QEasingCurve::OutQuad); QTimer::singleShot(jumpTime,[=](){ JumpUpOrDown(this->height()*0.5,QEasingCurve::OutBounce); }); } //向上或者向下跳跃一段距离 void MyButton::JumpUpOrDown(int dis,QEasingCurve cvrve) { QPropertyAnimation* anim=new QPropertyAnimation(this,"geometry"); anim->setDuration(jumpTime); anim->setStartValue(QRect(this->x(),this->y(),this->width(),this->height())); anim->setEndValue(QRect(this->x(),this->y()+dis,this->width(),this->height())); anim->setEasingCurve(cvrve); anim->start(); } //按下鼠标,切换到第二张图片 void MyButton::mousePressEvent(QMouseEvent *e) { if(this->hasPix2) { setIcon(pix2); } QPushButton::mousePressEvent(e); } //松开鼠标,恢复第一张图片 void MyButton::mouseReleaseEvent(QMouseEvent *e) { if(this->hasPix2) { setIcon(pix1); } QPushButton::mouseReleaseEvent(e); }
mycoin.h金币按钮
#ifndef MYCOIN_H #define MYCOIN_H #include <QWidget> #include <QPushButton> class MyCoin : public QPushButton { Q_OBJECT public: explicit MyCoin(int isGold,QWidget *parent = 0); bool isGold;//是否是金币 const int minFrame=1;//动画最小帧 const int maxFrame=8;//动画最大帧 int startFrame;//动画开始帧 int rowIndex;//行索引 int colIndex;//列索引 void Flip();//翻转硬币,修改状态,并播放动画 void ChangeIcon(int isGold);//根据data数据修改当前状态 signals: public slots: }; #endif // MYCOIN_H
mycoin.cpp金币按钮
#include "mycoin.h" #include <QPixmap> #include <QTimer> #include <QDebug> MyCoin::MyCoin(int isGold,QWidget *parent) : QPushButton(parent) { //是否是金币 this->isGold=isGold==1; //图片对象 QPixmap pix; //根据是银币还是金币,选择不同的图片 if(isGold) { pix=QPixmap(":/res/Coin0001.png"); } else { pix=QPixmap(":/res/Coin0008.png"); } //设置按钮大小 QSize size=QSize(pix.width(),pix.height()); setFixedSize(size); //设置图标及其大小 setIcon(pix); setIconSize(size); //设置显示样式(无边框) setStyleSheet("QPushButton{border:0px;}"); } //金币翻转函数,播放翻转动画,并修改相关属性值 void MyCoin::Flip() { //定义一个定时器对象 QTimer* timer=new QTimer(); //为定时器设置时间间隔,也就是翻转动画每一帧的持续时长 timer->setInterval(50); if(isGold) { //将起始帧设置为最小值(金币) startFrame= minFrame; connect(timer,&QTimer::timeout,[=](){ //每间隔一段时间,就更换一张图片,从而形成动画效果 setIcon(QPixmap(QString(":/res/Coin000%1.png").arg(startFrame++))); //完成翻转,定时器停止 if(startFrame>maxFrame) { timer->stop(); //金币变银币 isGold=!isGold; } }); } else { //将起始帧设置为最大值(银币) startFrame=maxFrame; connect(timer,&QTimer::timeout,[=](){ //每间隔一段时间,就更换一张图片,从而形成动画效果 setIcon(QPixmap(QString(":/res/Coin000%1.png").arg(startFrame--))); //完成翻转,定时器停止 if(startFrame<minFrame) { timer->stop(); //银币变金币 isGold=!isGold; } }); } //开始翻转时,禁用按钮 timer->start(); } void MyCoin::ChangeIcon(int isGold) { //如果要改变的状态就是当前状态,则不进行任何操作 if(this->isGold==(isGold==1)) { return; } //如果状态不同,就进行翻转 this->Flip(); }
res.qrc资源文件
<RCC> <qresource prefix="/"> <file>res/BackButton.png</file> <file>res/BackButtonSelected.png</file> <file>res/BoardNode.png</file> <file>res/Coin0001.png</file> <file>res/Coin0002.png</file> <file>res/Coin0003.png</file> <file>res/Coin0004.png</file> <file>res/Coin0005.png</file> <file>res/Coin0006.png</file> <file>res/Coin0007.png</file> <file>res/Coin0008.png</file> <file>res/LevelCompletedDialogBg.png</file> <file>res/LevelIcon.png</file> <file>res/MenuSceneBg.png</file> <file>res/MenuSceneStartButton.png</file> <file>res/OtherSceneBg.png</file> <file>res/PlayLevelSceneBg.png</file> <file>res/Title.png</file> <file>res/AnswerButton.png</file> <file>res/AnswerButtonSelected.png</file> <file>res/RestartButton.png</file> <file>res/RestartButtonSelected.png</file> <file>res/back.wav</file> <file>res/flip.wav</file> <file>res/select.wav</file> <file>res/start.wav</file> <file>res/answer.wav</file> <file>res/restart.wav</file> <file>res/succeed.wav</file> </qresource> </RCC>