目录
一.示例演示
二.制作思路
(一).准备资源及总体框架
(二).开始窗口
(三).选择关卡窗口
(四).游戏窗口
(五).优化
三.实现代码
(一).自制按钮类
(二).开始窗口
(三).选择关卡窗口
(四).金币类
(五).初始化金币数据类
(六).游戏窗口
一.示例演示
二.制作思路
(一).准备资源及总体框架
创建项目后,将资源加载通过创建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_NAMESPACE
class startWindow : public QMainWindow
{
Q_OBJECT
public:
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);
}
作为一个真正的程序员,首先应该尊重编程,热爱你所写下的程序,他是你的伙伴,而不是工具——未名
如有错误,敬请斧正