目录
一.示例演示
二.制作思路
(一).准备资源及总体框架
(二).开始窗口
(三).选择关卡窗口
(四).游戏窗口
(五).优化
三.实现代码
(一).自制按钮类
(二).开始窗口
(三).选择关卡窗口
(四).金币类
(五).初始化金币数据类
(六).游戏窗口
一.示例演示
二.制作思路
(一).准备资源及总体框架
创建项目后,将资源加载通过创建Qt设计师界面类加载进项目。
(资源已上传个人仓库,有需要请自取:Qt/12.23翻转金币/res · 纽盖特/git all - 码云 - 开源中国 (gitee.com))
大致上,我们需要一个开始游戏的窗口、选择关卡的窗口、玩游戏的窗口,共三个窗口。
(二).开始窗口
这个窗口需要使用菜单栏,因此可以用QMainWindow类。
此外要设置背景图片,可以使用绘图事件paintEvent完成。
窗口标题及图片直接在构造函数完成,使用setWindowTitle及setWindowIcon完成。
按钮要设置成点击后会向下弹并返回,可以专门定义一个按钮类,使用QPropertyAnimation特效类编写下降和上升的函数。
关卡窗口完成后,使用connect通过信号和槽完成点击按钮就关闭本窗口打开关卡窗口的操作。
(三).选择关卡窗口
与开始窗口一样,完成背景、标题、菜单栏的搭建。
难点在于关卡按钮和BACK按钮。
关卡按钮使用自制按钮类即可,但需要设置底层图片
该按钮通过QLabel标签类完成关卡号的书写。但这还不够,如果此时connect后点击按钮不会有任何反应,因为此时标签在按钮之上,鼠标相当于直接点在标签上。
需要使用鼠标穿透:lab->setAttribute(Qt::WA_TransparentForMouseEvents);
使用connect将关卡按钮与第三次游戏窗口连接。
BACK按钮同样使用自制按钮类,但此时不同的是点击后按钮要换图片:
对此,我们可以在自制按钮类中定义鼠标事件,点击事件mousePressEvent()内部将图片切换成第二个,释放事件mouseReleaseEvent()内部将图片切换成第一个。
之后将BACK按钮与开始窗口connect连接即可。
(四).游戏窗口
本层的背景、菜单栏、标题搭建与之前一样。
重点是翻转金币和成功动画的跳出。
首先要进一步优化本层BACK按钮,在第二层BACK按钮基础上在窗口构造函数中connect连接BACK按钮与lambda函数,lambda函数内部通过消息对话框QMessageBox完成是否退出的询问。
翻转金币的过程要先定义一个金币类,继承自QPushButton按钮类。内部要实现两个翻转函数,分别是翻成金币和银币,具体翻转过程通过金币类内部定义两个QTimer定时器,当需要翻转时发送start函数,使用connect让lambda函数接收,函数内部根据QTimer定时翻转一小部分,直到全部翻完,stop相关QTimer。
判定成功通过在金币类中定义bool类型成员,翻成金币为true,银币为false。遍历一遍全部是true时说明游戏成功。
初始化游戏硬币颜色可以再定义一个数据类(dataofcoin.h中),这里面定义一个QMap类,first是记录的层数,second是二维数组,使用0和1分别代表银币和金币。当游戏窗口初始化时,通过调用数据类,根据窗口是哪一关(游戏窗口类有一个成员,当关卡层点击关卡后,该成员会记录这是第几关),调用对应二维数组,完成硬币颜色初始化。
游戏成功后,需要锁定硬币,不能再通过点击硬币翻转同时跳出成功动画。
锁定硬币通过在自制按钮类中定义一个bool成员,当检查游戏成功时设置成true。同时按钮类的鼠标事件判断该成员如果为true就直接退出事件,即点击无效。
跳出成功动画是当检测到成功后,先加载图片到游戏窗口之外(需要注意在构造函数外控件需要调用show函数才能显示,即便是构造函数内的lambda函数内也一样),再通过特效类QPropertyAnimation完成图片的下降。
(五).优化
优化主要有两个:完成音效和完成窗口移动。
音效通过使用QSound类,加载音效路径后调用play函数完成。
窗口移动优化的意思是:假如此时在第二层,如果移动窗口,那么当返回 第一层后窗口就自动回到之前的位置而不是在我们移动后的位置。
优化方式就是在每一层切回的connect槽函数中先同步切回前上一层窗口的位置,本层窗口打开时直接就是该位置了。具体是通过setGeometry()窗口成员函数完成。
三.实现代码
(一).自制按钮类
//mybutton.h
#ifndef MYBUTTON_H
#define MYBUTTON_H#include <QMainWindow>
#include<QPushButton>
#include<QEvent>class myButton : public QPushButton
{Q_OBJECT
public:explicit myButton(QString firstPath, QString secondPath = "");void putDown();void putUp();void mousePressEvent(QMouseEvent* ev);void mouseReleaseEvent(QMouseEvent* ev);signals:void returnToStart();void returnToSelect();
private:QString normalPath;QString pressPath;
};#endif // MYBUTTON_H
//mybutton.cpp
#include "mybutton.h"
#include<QPixmap>
#include<QIcon>
#include<QPropertyAnimation>
#include<QSound>myButton::myButton(QString firstPath, QString secondPath)
{normalPath = firstPath;pressPath = secondPath;QPixmap pix;//按钮图片pix.load(firstPath);this->setFixedSize(pix.width(), pix.height());//设置按钮大小this->setStyleSheet("QPushButton{border:Opx}");//解除图片规则限制this->setIcon(pix);this->setIconSize(QSize(pix.width(), pix.height()));//设置按钮图片大小}void myButton::mousePressEvent(QMouseEvent* ev){if(pressPath != ""){QSound* sound = new QSound(":/res/TapButtonSound.wav", this);sound->play();QPixmap pix;pix.load(":/res/BackButtonSelected.png");this->setIcon(pix);}return QPushButton::mousePressEvent(ev);
}
void myButton::mouseReleaseEvent(QMouseEvent* ev){if(pressPath != ""){QPixmap pix;pix.load(":/res/BackButton.png");this->setIcon(pix);//emit returnToSelect();//发出返回信号}return QPushButton::mouseReleaseEvent(ev);
}void myButton::putDown(){//设置特效QPropertyAnimation* pro = new QPropertyAnimation(this, "geometry");pro->setDuration(500);//设置特效时间//设置特效开始与结束pro->setStartValue(QRect(x(), y(), width(), height()));pro->setEndValue(QRect(x(), y() + 10, width(), height()));//设置特效弹跳效果pro->setEasingCurve(QEasingCurve::OutBounce);pro->start();
}
void myButton::putUp(){//设置特效QPropertyAnimation* pro = new QPropertyAnimation(this, "geometry");pro->setDuration(500);//设置特效时间//设置特效开始与结束pro->setStartValue(QRect(x(), y() + 10, width(), height()));pro->setEndValue(QRect(x(), y(), width(), height()));//设置特效弹跳效果pro->setEasingCurve(QEasingCurve::OutBounce);pro->start();
}
(二).开始窗口
//startwindow.h
#ifndef STARTWINDOW_H
#define STARTWINDOW_H
#include"mybutton.h"
#include"selectwindow.h"#include <QMainWindow>
#include<QEvent>
QT_BEGIN_NAMESPACE
namespace Ui { class startWindow; }
QT_END_NAMESPACEclass startWindow : public QMainWindow
{Q_OBJECTpublic:startWindow(QWidget *parent = nullptr);~startWindow();void paintEvent(QPaintEvent*);
private:Ui::startWindow *ui;
};
#endif // STARTWINDOW_H
//selectwindow.cpp
#include "selectwindow.h"#include<QIcon>
#include<QPixmap>
#include<QPainter>
#include<QMenuBar>
#include<QDebug>
#include<QLabel>
#include<QTimer>selectWindow::selectWindow(QMainWindow *parent) : QMainWindow(parent)
{setFixedSize(360, 620);setWindowIcon(QIcon(":/res/Coin0001.png"));setWindowTitle("Coin Filp");//创建菜单QMenuBar* bar = new QMenuBar(this);setMenuBar(bar);QMenu* start = bar->addMenu("开始");QAction* quit = start->addAction("结束");connect(quit, &QAction::triggered, [=]{close();});//创建关卡选择for(int i = 0; i < 9; i++){//每一关按钮myButton* but = new myButton(":/res/LevelIcon.png");but->setParent(this);but->move(50 + i % 3 * 100, 180 + i / 3 * 100);//按钮上的标号QLabel* lab = new QLabel(this);lab->setText(QString().number(i + 1));lab->setFixedSize(but->width(), but->height());lab->move(50 + i % 3 * 100, 180 + i / 3 * 100);lab->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);//设置水平垂直居中QFont font;//字体设置font.setBold(true);font.setFamily("YouYuan");font.setPointSize(10);lab->setFont(font);//按钮穿透lab->setAttribute(Qt::WA_TransparentForMouseEvents);//建立关卡按钮连接connect(but, &QPushButton::clicked, [=]{QTimer::singleShot(500, this, [=]{gameWindow* game = new gameWindow(i + 1);game->setGeometry(geometry());game->show();this->hide();//游戏界面如果按下返回按钮connect(game, &gameWindow::returnToSelect, [=]{this->setGeometry(game->geometry());//防止窗口位置与上一层不同this->show();delete game;});});});}//返回按钮myButton* ret = new myButton(":/res/BackButton.png", ":/res/BackButtonSelected.png");ret->setParent(this);ret->move(width() - ret->width(), height() - ret->height());connect(ret, &QPushButton::clicked, [=]{emit returnToStart();});}void selectWindow::paintEvent(QPaintEvent*){QPixmap pix;bool judge = pix.load(":/res/OtherSceneBg.png");if(judge == false) qDebug() << "Load Error";QPainter p(this);p.drawPixmap(0, 0, width(), height(), pix);pix.load(":/res/Title.png");p.drawPixmap(width() * 0.5 - pix.width() * 0.5, 30, pix);}
(三).选择关卡窗口
//selectwindow.h
#ifndef SELECTWINDOW_H
#define SELECTWINDOW_H
#include"mybutton.h"
#include"gamewindow.h"#include <QMainWindow>class selectWindow : public QMainWindow
{Q_OBJECT
public:explicit selectWindow(QMainWindow *parent = nullptr);void paintEvent(QPaintEvent*);signals:void returnToStart();};
#endif // SELECTWINDOW_H
//selectwindow.cpp
#include "selectwindow.h"#include<QIcon>
#include<QPixmap>
#include<QPainter>
#include<QMenuBar>
#include<QDebug>
#include<QLabel>
#include<QTimer>selectWindow::selectWindow(QMainWindow *parent) : QMainWindow(parent)
{setFixedSize(360, 620);setWindowIcon(QIcon(":/res/Coin0001.png"));setWindowTitle("Coin Filp");//创建菜单QMenuBar* bar = new QMenuBar(this);setMenuBar(bar);QMenu* start = bar->addMenu("开始");QAction* quit = start->addAction("结束");connect(quit, &QAction::triggered, [=]{close();});//创建关卡选择for(int i = 0; i < 9; i++){//每一关按钮myButton* but = new myButton(":/res/LevelIcon.png");but->setParent(this);but->move(50 + i % 3 * 100, 180 + i / 3 * 100);//按钮上的标号QLabel* lab = new QLabel(this);lab->setText(QString().number(i + 1));lab->setFixedSize(but->width(), but->height());lab->move(50 + i % 3 * 100, 180 + i / 3 * 100);lab->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);//设置水平垂直居中QFont font;//字体设置font.setBold(true);font.setFamily("YouYuan");font.setPointSize(10);lab->setFont(font);//按钮穿透lab->setAttribute(Qt::WA_TransparentForMouseEvents);//建立关卡按钮连接connect(but, &QPushButton::clicked, [=]{QTimer::singleShot(500, this, [=]{gameWindow* game = new gameWindow(i + 1);game->setGeometry(geometry());game->show();this->hide();//游戏界面如果按下返回按钮connect(game, &gameWindow::returnToSelect, [=]{this->setGeometry(game->geometry());//防止窗口位置与上一层不同this->show();delete game;});});});}//返回按钮myButton* ret = new myButton(":/res/BackButton.png", ":/res/BackButtonSelected.png");ret->setParent(this);ret->move(width() - ret->width(), height() - ret->height());connect(ret, &QPushButton::clicked, [=]{emit returnToStart();});}void selectWindow::paintEvent(QPaintEvent*){QPixmap pix;bool judge = pix.load(":/res/OtherSceneBg.png");if(judge == false) qDebug() << "Load Error";QPainter p(this);p.drawPixmap(0, 0, width(), height(), pix);pix.load(":/res/Title.png");p.drawPixmap(width() * 0.5 - pix.width() * 0.5, 30, pix);}
(四).金币类
//coin.h
#ifndef CION_H
#define CION_H
#include <QWidget>
#include<QPushButton>
#include<QTimer>
#include<QPixmap>class Coin : public QPushButton
{Q_OBJECT
public:Coin(int i, int j);void Filp();QTimer* time1;QTimer* time2;int subX;int subY;int min = 1;int max = 8;bool goldFace;bool filpNow = false;bool isWin = false;
signals:};
#endif // CION_H
//coin.cpp
#include "coin.h"#include<QTimer>
#include<QDebug>Coin::Coin(int i, int j):subX(i),subY(j),time1(new QTimer(this)),time2(new QTimer(this))
{connect(time1, &QTimer::timeout, [=]{QPixmap pix;pix.load(QString(":/res/Coin000%1.png").arg(min++));setIconSize(QSize(pix.width(), pix.height()));setIcon(pix);if(min > max){min = 1;filpNow = false;time1->stop();}});connect(time2, &QTimer::timeout, [=]{QPixmap pix;pix.load(QString(":/res/Coin000%1.png").arg(max--));setIconSize(QSize(pix.width(), pix.height()));setIcon(pix);if(max < min){max = 8;filpNow = false;time2->stop();}});
}void Coin::Filp(){if(filpNow == false && isWin == false){filpNow = true;if(goldFace){//翻成银币goldFace = false;time1->start(30);}else{//翻成金币goldFace = true;time2->start(30);}}else{return;}
}
(五).初始化金币数据类
//dataofcoin.h
#ifndef DATAOFCOIN_H
#define DATAOFCOIN_H
#include <QObject>
#include<QMap>
#include<QVector>class Data : public QObject
{Q_OBJECT
public:explicit Data(QObject *parent = nullptr);QMap<int, QVector<QVector<int>>> dataMap;//记载每一关初始金币
signals:};
#endif // DATAOFCOIN_H
//dataofcoin.cpp
#include "dataofcoin.h"Data::Data(QObject *parent) : QObject(parent)
{//第一关QVector<QVector<int>> array = {{1, 1, 1, 1},{1, 1, 0, 1},{1, 0, 0, 0},{1, 1, 0, 1}};dataMap.insert(1, array);//第二关array = {{1, 0, 1, 1},{0, 0, 1, 1},{1, 1, 0, 0},{1, 1, 0, 1}};dataMap.insert(2, array);//第三关array = {{0, 0, 0, 0},{0, 1, 1, 0},{0, 1, 1, 0},{0, 0, 0, 0}};dataMap.insert(3, array);//第四关array = {{0, 1, 1, 0},{1, 1, 1, 1},{0, 0, 0, 0},{0, 0, 0, 0}};dataMap.insert(4, array);//第五关array = {{1, 0, 0, 1},{1, 0, 0, 1},{1, 0, 0, 1},{1, 0, 0, 1}};dataMap.insert(5, array);//第六关array = {{0, 0, 1, 0},{1, 1, 0, 0},{0, 0, 1, 1},{0, 1, 0, 0}};dataMap.insert(6, array);//第七关array = {{0, 1, 1, 0},{1, 0, 0, 1},{1, 0, 0, 1},{0, 1, 1, 0}};dataMap.insert(7, array);//第八关array = {{0, 0, 1, 0},{0, 0, 0, 1},{0, 1, 0, 0},{0, 0, 0, 0}};dataMap.insert(8, array);//第九关array = {{0, 0, 0, 0},{0, 0, 0, 0},{0, 0, 0, 0},{0, 0, 0, 0}};dataMap.insert(9, array);
}
(六).游戏窗口
//gamewindow.h
#ifndef GAMEWINDOW_H
#define GAMEWINDOW_H
#include"mybutton.h"
#include"coin.h"
#include"dataofcoin.h"#include <QMainWindow>
#include<QPaintEvent>
#include<QVector>
#include<QPropertyAnimation>class gameWindow : public QMainWindow
{Q_OBJECT
public:explicit gameWindow(int level);void paintEvent(QPaintEvent*);signals:void returnToSelect();
private:void judgeWin();int gameLevel;bool isWinForButton = false;QVector<QVector<Coin*>> arrCoin;};#endif // GAMEWINDOW_H
//gamewindow.cpp
#include "gamewindow.h"
#include<QMenuBar>
#include<QPainter>
#include<QIcon>
#include<QPixmap>
#include<QDebug>
#include<QLabel>
#include<QFont>
#include<QMessageBox>
#include<QSound>gameWindow::gameWindow(int level):QMainWindow(),gameLevel(level)
{setFixedSize(360, 620);setWindowIcon(QIcon(":/res/Coin0001.png"));setWindowTitle("Coin Filp");//创建菜单QMenuBar* bar = new QMenuBar(this);setMenuBar(bar);QMenu* start = bar->addMenu("开始");QAction* quit = start->addAction("结束");connect(quit, &QAction::triggered, [=]{close();});//返回按钮myButton* ret = new myButton(":/res/BackButton.png", ":/res/BackButtonSelected.png");ret->setParent(this);ret->move(width() - ret->width(), height() - ret->height());connect(ret, &QPushButton::clicked, [=]{//如果完成游戏或者消息对话框选择yes就emit信号函数给关卡层完成游戏层关闭if(isWinForButton ||QMessageBox::Yes == QMessageBox::question(this, "warning",\"确定回到选择界面么?",QMessageBox::Yes | QMessageBox::No)){emit returnToSelect();//发送信号函数}});//关卡等级标签QLabel* lab = new QLabel(this);QFont font;font.setFamily("YouYuan");font.setPointSize(20);font.setBold(true);lab->setFont(font);lab->setText(QString("Level: %1").arg(gameLevel));lab->setFixedSize(ret->width() * 3, ret->height() * 2);lab->move(20, height() - lab->height());//创建金币矩阵Data data;for(int i = 0; i < 4; i++){arrCoin.push_back(QVector<Coin*>(4));}for(int i = 0; i < 4; i++){for(int j = 0; j < 4; j++){//创建背景QPixmap pix;pix.load(":/res/BoardNode.png");QLabel* lab = new QLabel(this);lab->setGeometry(0, 0, pix.width(), pix.height());lab->setPixmap(pix);lab->move(67 + j % 4 * pix.width() * 1.2, 200 + i % 4 * pix.height() * 1.2);//创建硬币QPixmap pixCoin;Coin* coin = new Coin(i, j);coin->setParent(this);arrCoin[i][j] = coin;if(data.dataMap[gameLevel][i][j] == 1){//建金币pixCoin.load(":/res/Coin0001.png");coin->goldFace = true;}else{//建银币pixCoin.load(":/res/Coin0008.png");coin->goldFace = false;}//设置币的位置coin->setFixedSize(pixCoin.width(), pixCoin.height());coin->setStyleSheet("QPushButton{border:Opx}");coin->setIcon(pixCoin);coin->setIconSize(QSize(pixCoin.width(), pixCoin.height()));coin->move(70 + j % 4 * pix.width() * 1.2, 203 + i % 4 * pix.height() * 1.2);connect(coin, &QPushButton::clicked, [=]{//硬币翻转QSound* sound = new QSound(":/res/ConFlipSound.wav", this);sound->play();coin->Filp();QTimer::singleShot(200, this, [=]{//延时翻转其他硬币//翻转其他硬币if(i < 3){//下arrCoin[i + 1][j]->Filp();}if(i > 0){arrCoin[i - 1][j]->Filp();}if(j < 3){//右arrCoin[i][j + 1]->Filp();}if(j > 0){arrCoin[i][j - 1]->Filp();}//判断是否胜利judgeWin();});});}}}void gameWindow::judgeWin(){for(auto& vec : arrCoin){for(auto& coin : vec){if(coin->goldFace == false) return;}}//此时已经胜利QTimer::singleShot(200, this, [=]{//播放成功音乐QSound* sound = new QSound(":/res/LevelWinSound.wav", this);sound->play();});for(auto& vec : arrCoin){for(auto& coin : vec){coin->isWin = true;}}isWinForButton = true;QPixmap pixWin;//加载成功图片pixWin.load(":/res/LevelCompletedDialogBg.png");QLabel* labWin = new QLabel(this);labWin->setFixedSize(pixWin.width(), pixWin.height());labWin->setPixmap(pixWin);labWin->setGeometry(0, 0, pixWin.width(), pixWin.height());labWin->move(width() * 0.5 - pixWin.width() * 0.5, -pixWin.height());labWin->show();//使用show才能展示//完成特效QPropertyAnimation* pro = new QPropertyAnimation(labWin, "geometry");pro->setDuration(1000);pro->setStartValue(QRect(labWin->x(), labWin->y(),\labWin->width(), labWin->height()));pro->setEndValue(QRect(labWin->x(), 90,\labWin->width(), labWin->height()));pro->setEasingCurve(QEasingCurve::OutBounce);pro->start();
}void gameWindow::paintEvent(QPaintEvent*){//绘图事件,创建背景QPixmap pix;pix.load(":/res/OtherSceneBg.png");QPainter p(this);p.drawPixmap(0, 0, width(), height(), pix);//添加标题图片pix.load(":/res/Title.png");pix = pix.scaled(pix.width() * 0.5, pix.height() * 0.5);//图片缩放p.drawPixmap(0, 30, pix);
}
作为一个真正的程序员,首先应该尊重编程,热爱你所写下的程序,他是你的伙伴,而不是工具——未名
如有错误,敬请斧正