目录
4、创建选择关卡场景(chooselevelscene.cpp/.h)
一、实现效果
二、实现过程
1、创建项目、添加项目资源
如下为所需的图片资源与完成的工程目录
2、创建主场景(mainscene.cpp/.h)
设置固定大小、标题、图标、背景、菜单栏退出项、开始按钮等
#include "mainscene.h"
#include "ui_mainscene.h"
#include "QPainter"
#include "QPixmap"
#include "mypushbutton.h"
#include "QDebug"
#include "chooselevelscene.h"
#include "QTimer"
#include "QSound"//多媒体模块下的音效头文件
mainscene::mainscene(QWidget *parent) : QMainWindow(parent), ui(new Ui::mainscene)
{
ui->setupUi(this);
//配置主场景
setFixedSize(320,588);//设置固定大小
setWindowIcon(QIcon(":/res/Coin0001.png"));//设置图标
setWindowTitle("翻金币主场景");//设置标题
//菜单栏-->退出选项 的实现
connect(ui->actionQuit, &QAction::triggered, [=](){
this->close();
});
//准备开始按钮的音效
QSound *startsound = new QSound(":/res/TapButtonSound.wav", this);
//实例化选择关卡场景
chooseScene = new ChooseLevelScene;
//监听选择关卡的返回按钮信号
connect(chooseScene, &ChooseLevelScene::chooseSceneBack, this, [=](){
chooseScene->hide();//隐藏选择关卡
this->setGeometry(chooseScene->geometry());//由选择场景返回时,设置主场景的位置,不然会乱动
this->show();//重新显示主场景
});
//开始按钮,使用自定义的PushButton
MyPushbutton *startbtn = new MyPushbutton(":/res/MenuSceneStartButton.png");
startbtn->setParent(this);
startbtn->move(this->width()*0.5 - startbtn->width()*0.5, this->height()*0.7);
connect(startbtn, &MyPushbutton::clicked, [=](){
qDebug() <<"点击了开始";
startbtn->zoom1();
startbtn->zoom2();
//播放音效
startsound->play();
//startsound->setLoops(-1);//设置循环,如果是-1表示无限循环
//延时进入选择关卡,才能看见弹跳的效果
QTimer::singleShot(100,this,[=](){
this->hide();//隐藏主场景
chooseScene->setGeometry(this->geometry());//进入选择场景时,设置选择场景的位置,不然会乱动
chooseScene->show();//显示选择关卡的场景
});
});
}
mainscene::~mainscene()
{
delete ui;
}
//重写paintEvent事件,画背景图
void mainscene::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QPixmap pix;
pix.load(":/res/PlayLevelSceneBg.png");
painter.drawPixmap(0,0,this->width(), this->height(), pix);
pix.load(":/res/Title.png");
pix = pix.scaled(pix.width()*0.5, pix.height()*0.5);//对图片进行缩放
painter.drawPixmap(10,30,pix);
}
3、自定义开始按钮(mypushbutton.cpp/.h)
1、封装自定义的按钮 MyPushButton
2、构造函数(默认显示图片、按下后显示的图片)
3、测试开始按钮
4、开始制作特效
5、zoom1 向下跳
6、zoom2向上跳
#include "mypushbutton.h"
#include "QDebug"
#include "QPropertyAnimation"
//构造函数,参数:正常显示的图片路径 按下后显示的图片路径
MyPushbutton::MyPushbutton(QString normalImg, QString pressImg)
{
this->normalImgPath = normalImg;
this->pressImgPath = pressImg;
QPixmap pix;
bool ret = pix.load(normalImgPath);
if (!ret){
qDebug() <<"图片加载失败";
return;
}
//设置图片固定大小
this->setFixedSize(pix.width(), pix.height());
//设置不规则图片的样式
this->setStyleSheet("QPushButton{border:0px}");
//设置图标
this->setIcon(pix);
//设置图标大小
this->setIconSize(QSize(pix.width(), pix.height()));
}
//向下跳
void MyPushbutton::zoom1()
{
//创建动态对象
QPropertyAnimation *animation = new QPropertyAnimation(this, "geometry");
//设置动画时间间隔
animation->setDuration(100);
//起始位置
animation->setStartValue(QRect(this->x(), this->y(), this->width(), this->height()));
//结束位置
animation->setEndValue(QRect(this->x(), this->y()+10, this->width(), this->height()));
//设置弹跳曲线
animation->setEasingCurve(QEasingCurve::OutBounce);
//执行动画
animation->start();
}
//向上跳
void MyPushbutton::zoom2()
{
//创建动态对象
QPropertyAnimation *animation = new QPropertyAnimation(this, "geometry");
//设置动画时间间隔
animation->setDuration(100);
//起始位置
animation->setStartValue(QRect(this->x(), this->y()+10, this->width(), this->height()));
//结束位置
animation->setEndValue(QRect(this->x(), this->y(), this->width(), this->height()));
//设置弹跳曲线
animation->setEasingCurve(QEasingCurve::OutBounce);
//执行动画
animation->start();
}
void MyPushbutton::mousePressEvent(QMouseEvent *e)
{
if (this->pressImgPath != "")//传入的按下按钮不为空,表示需要切换图片
{
QPixmap pix;
bool ret = pix.load(this->pressImgPath);
if (!ret){
qDebug() <<"图片加载失败";
return;
}
//设置图片固定大小
this->setFixedSize(pix.width(), pix.height());
//设置不规则图片的样式
this->setStyleSheet("QPushButton{border:0px}");
//设置图标
this->setIcon(pix);
//设置图标大小
this->setIconSize(QSize(pix.width(), pix.height()));
}
//让父类执行其他内容
return QPushButton::mousePressEvent(e);
}
void MyPushbutton::mouseReleaseEvent(QMouseEvent *e)
{
if (this->pressImgPath != "")//传入的按下按钮不为空,表示按下的时候切换图片了,弹起的时候需要切换回去
{
QPixmap pix;
bool ret = pix.load(this->normalImgPath);
if (!ret){
qDebug() <<"图片加载失败";
return;
}
//设置图片固定大小
this->setFixedSize(pix.width(), pix.height());
//设置不规则图片的样式
this->setStyleSheet("QPushButton{border:0px}");
//设置图标
this->setIcon(pix);
//设置图标大小
this->setIconSize(QSize(pix.width(), pix.height()));
}
//让父类执行其他内容
return QPushButton::mouseReleaseEvent(e);
}
4、创建选择关卡场景(chooselevelscene.cpp/.h)
1、点击开始按钮后,延时进入选择关卡,才能看见弹跳的效果
2、配置选择关卡场景(固定大小、标题、图标、背景、菜单栏退出项、返回按钮)
3、编写返回按钮特效:按下后和弹起后更换背景
4、开始场景与选择场景的切换:点击选择关卡场景的返回按钮,发送一个自定义的信号,在主场景中,监听选择关卡场景的自定义的信号,再隐藏选择场景,显示主场景
5、选择关卡中的选择按钮创建:利用一个for循环将所有按钮布置到场景中;在按钮上设置一个QLabel显示关卡数;QLabel设置大小、字体、显示文字、对齐方式、鼠标穿透;给每个按钮监听点击事件
#include "chooselevelscene.h"
#include "QMenuBar"
#include "QPainter"
#include "QPixmap"
#include "QDebug"
#include "mypushbutton.h"
#include "QTimer"
#include "QLabel"
#include "QSound"
ChooseLevelScene::ChooseLevelScene(QWidget *parent) : QMainWindow(parent)
{
//配置选择关卡场景
this->setFixedSize(320,588);
//设置图标
this->setWindowIcon(QPixmap(":/res/Coin0001.png"));
//设置标题
this->setWindowTitle("选择关卡场景");
//创建菜单栏
QMenuBar *bar = menuBar();
setMenuBar(bar);
QMenu *startMenu = bar->addMenu("开始");//创建开始菜单项
QAction *quitAction = startMenu->addAction("退出"); //场景退出的菜单项
connect(quitAction, &QAction::triggered, [=](){
this->close();
});
//选择关卡音效
QSound *choosesound = new QSound(":/res/TapButtonSound.wav", this);
//返回音效
QSound *backsound = new QSound(":/res/BackButtonSound.wav", this);
//创建返回按钮
MyPushbutton *backbtn = new MyPushbutton(":/res/BackButton.png", ":/res/BackButtonSelected.png");
backbtn->setParent(this);
backbtn->move(this->width()-backbtn->width(), this->height()-backbtn->height());
//点击之后返回主场景
connect(backbtn, &MyPushbutton::clicked, [=](){
//播放返回音效
backsound->play();
qDebug() <<"选择场景点击了返回按钮";
//需要告诉主场景,选择场景返回了,主场景监听 chooseScene 的返回
QTimer::singleShot(100, this, [=](){
emit this->chooseSceneBack();//延时返回
});
});
//场景选择按钮
for (int i=0; i<20; i++)
{
//创建按钮
MyPushbutton *menuBtn = new MyPushbutton(":/res/LevelIcon.png");
menuBtn->setParent(this);
menuBtn->move(25+i%4*70, 130+i/4*70);
//创建按钮文本
QLabel *label = new QLabel;
label->setParent(this);
label->setFixedSize(menuBtn->width(), menuBtn->height());
label->setText(QString::number(i+1));
label->move(25+i%4*70, 130+i/4*70);
label->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter); //设置文字对齐方式
label->setAttribute(Qt::WA_TransparentForMouseEvents); //设置让鼠标进行穿透!!!!这样menuBtn才能收到点击事件
//监听每个按钮的点击事件
connect(menuBtn, &MyPushbutton::clicked, [=](){
//播放音效
choosesound->play();
QString str = QString("选择的关卡是 %1 关").arg(i+1);
qDebug() <<str.toUtf8().data();
//进入到游戏场景
this->hide();//隐藏选择场景
this->play = new PlayScene(i+1);
this->play->setGeometry(this->geometry());//进入翻金币场景时,设置其位置
this->play->show();//进入游戏场景
//监听游戏场景的返回信号
connect(play, &PlayScene::chooseSceneBack, [=](){
this->setGeometry(this->play->geometry());//翻金币场景返回时,设置选择场景的位置
this->show();//展示选择场景,这里不会立马显示出来????????
delete this->play;//删除翻金币场景
this->play = NULL;
});
});
}
}
//重写绘图事件,绘制背景
void ChooseLevelScene::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QPixmap pix;
//加载背景
pix.load(":/res/OtherSceneBg.png");
painter.drawPixmap(0,0,this->width(),this->height(),pix);
//加载标题
pix.load(":/res/Title.png");
//pix = pix.scaled(pix.width()*0.5, pix.height()*0.5);//对图片进行缩放
painter.drawPixmap(10,30,pix);
}
5、创建翻金币场景(playscene.cpp/.h)
1、点击选择关卡按钮后,进入到翻金币游戏场景
2、配置游戏场景(固定大小、标题、图标、背景、菜单栏退出项、返回按钮、关卡号显示Label)
#include "playscene.h"
#include "QMenuBar"
#include "QPainter"
#include "QPixmap"
#include "QDebug"
#include "mypushbutton.h"
#include "QTimer"
#include "QLabel"
#include "mycoin.h"
#include "dataconfig.h"
#include <QPropertyAnimation>
#include <QSound>
//PlayScene::PlayScene(QWidget *parent) : QMainWindow(parent)
//{
//}
PlayScene::PlayScene(int levelNum)
{
QString str = QString("进入了第 %1 关").arg(levelNum);
qDebug() <<str.toUtf8().data();
this->levelIndex = levelNum;
//初始化游戏场景
this->setFixedSize(320,588);
this->setWindowIcon(QPixmap(":/res/Coin0001.png"));
this->setWindowTitle("翻金币场景");
//创建菜单栏
QMenuBar *bar = menuBar();
setMenuBar(bar);
QMenu *startMenu = bar->addMenu("开始");//创建开始菜单
QAction *quitAction = startMenu->addAction("退出"); //场景退出的菜单项
connect(quitAction, &QAction::triggered, [=](){
this->close();
});
//胜利的音效
QSound *winsound = new QSound(":/res/LevelWinSound.wav", this);
//翻金币音效
QSound *flipsound = new QSound(":/res/ConFlipSound.wav", this);
//返回音效
QSound *backsound = new QSound(":/res/BackButtonSound.wav", this);
//创建返回按钮
MyPushbutton *backbtn = new MyPushbutton(":/res/BackButton.png", ":/res/BackButtonSelected.png");
backbtn->setParent(this);
backbtn->move(this->width()-backbtn->width(), this->height()-backbtn->height());
//点击之后返回选择场景
connect(backbtn, &MyPushbutton::clicked, [=](){
//播放返回音效
backsound->play();
qDebug() <<"翻金币场景点击了返回按钮";
//需要告诉选择场景,翻金币场景返回了,选择d场景监听 chooseScene 的返回
QTimer::singleShot(10, this, [=](){
emit this->chooseSceneBack();//延时返回
});
});
//创建显示关卡的文本
QLabel *label = new QLabel;
QFont font;
font.setFamily("华文新魏");
font.setPointSize(20);
QString str1 = QString("Level: %1").arg(this->levelIndex);
label->setParent(this);
label->setFont(font);
label->setText(str1);
label->setGeometry(30, this->height()-50, 130, 50);
//初始化每个关卡的二维数组,记录金币的正反面状态
dataconfig config;
for (int i=0; i<4; i++)
{
for (int j=0; j<4; j++)
{
this->gameArray[i][j] = config.mData[this->levelIndex][i][j];
}
}
//胜利图片显示
QLabel *winLabel = new QLabel;
QPixmap tmppix;
tmppix.load(":/res/LevelCompletedDialogBg.png");
winLabel->setGeometry(0,0,tmppix.width(),tmppix.height());
winLabel->setPixmap(tmppix);
winLabel->setParent(this);
winLabel->move((this->width()-tmppix.width())*0.5, -tmppix.height());
//显示金币
for (int i=0; i<4; i++)
{
for (int j=0; j<4; j++)
{
//显示金币的背景图片
QPixmap pix = QPixmap(":/res/BoardNode.png");
QLabel *label = new QLabel;
label->setGeometry(0,0,pix.width(),pix.height());
label->setPixmap(pix);
label->setParent(this);
label->move(40+i*60, 200+j*60);
//创建金币
QString str;
if (this->gameArray[i][j] == 1){
str = ":/res/Coin0001.png";
} else {
str = ":/res/Coin0008.png";
}
MyCoin *coin = new MyCoin(str);
coin->setParent(this);
coin->move(44+i*60, 205+j*60);
//给金币的属性赋值
coin->posX = i;//记录X坐标
coin->posY = j;//记录y坐标
coin->flag = gameArray[i][j];//记录正反标志 1正面 0反面
//记录金币的句柄,以便后期的维护
this->coinbtns[i][j] = coin;
//监听金币按钮的点击事件,进行翻转
connect(coin, &MyCoin::clicked, [=](){
//播放翻金币音效
flipsound->play();
//点击金币后,在翻金币前,先禁用其他金币的点击响应,否则手速快会出现bug
for (int i=0; i<4; i++)
{
for (int j=0; j<4; j++)
{
this->coinbtns[i][j]->isWin = true;
}
}
//翻转点击的金币
coin->changeFlag();
this->gameArray[coin->posX][coin->posY] = (this->gameArray[coin->posX][coin->posY] == 0)?1:0;
//延时 翻转周围金币
QTimer::singleShot(300, this, [=](){
if (coin->posX+1 <= 3)//翻转右侧金币
{
this->coinbtns[coin->posX+1][coin->posY]->changeFlag();
this->gameArray[coin->posX+1][coin->posY] = (this->gameArray[coin->posX+1][coin->posY] == 0)?1:0;
}
if (coin->posX-1 >= 0)//翻转左侧金币
{
this->coinbtns[coin->posX-1][coin->posY]->changeFlag();
this->gameArray[coin->posX-1][coin->posY] = (this->gameArray[coin->posX-1][coin->posY] == 0)?1:0;
}
if (coin->posY-1 >= 0)//翻转上侧金币
{
this->coinbtns[coin->posX][coin->posY-1]->changeFlag();
this->gameArray[coin->posX][coin->posY-1] = (this->gameArray[coin->posX][coin->posY-1] == 0)?1:0;
}
if (coin->posY+1 <= 3)//翻转下侧金币
{
this->coinbtns[coin->posX][coin->posY+1]->changeFlag();
this->gameArray[coin->posX][coin->posY+1] = (this->gameArray[coin->posX][coin->posY+1] == 0)?1:0;
}
//翻转完所有金币后,才启用其他金币的点击响应,否则手速快会出现bug
for (int i=0; i<4; i++)
{
for (int j=0; j<4; j++)
{
this->coinbtns[i][j]->isWin = false;
}
}
//判断是否胜利
this->isWin = true;
for (int i=0; i<4; i++)
{
for (int j=0; j<4; j++)
{
if (this->gameArray[i][j] == false)
{
this->isWin = false;
break;
}
}
}
if (this->isWin == true)
{
//播放胜利音效
winsound->play();
qDebug() <<"游戏胜利";
//将胜利的图片降下来
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()+110, winLabel->width(), winLabel->height()));
//设置缓和曲线
animation->setEasingCurve(QEasingCurve::OutBounce);
//执行动画
animation->start();
//胜利后 将每个btn的标志都改为true,再点击按钮,不做响应
for (int i=0; i<4; i++)
{
for (int j=0; j<4; j++)
{
this->coinbtns[i][j]->isWin = true;
}
}
}
});
});
}
}
}
void PlayScene::paintEvent(QPaintEvent *)
{
QPixmap pix;
QPainter painter(this);
//加载背景
pix.load(":/res/PlayLevelSceneBg.png");
painter.drawPixmap(0,0,this->width(),this->height(),pix);
//加载标题
pix.load(":/res/Title.png");
pix = pix.scaled(pix.width()*0.5, pix.height()*0.5);//对图片进行缩放
painter.drawPixmap(10,30,pix);
}
6、创建金币(mycoin.cpp/.h)
1、先将金币的背景图像放入到playScene中
2、创建MyCoin自定义金币按钮类
3、MyCoin::MyCoin(QString btnImg) 构造函数中传入默认显示的图片金币
4、在playScene中创建所有的金币按钮
#include "mycoin.h"
#include "QString"
#include "QDebug"
#include "QPixmap"
#include "QPushButton"
//MyCoin::MyCoin(QWidget *parent) : QWidget(parent)
//{
//}
MyCoin::MyCoin(QString btnImg)
{
QPixmap pix;
bool ret = pix.load(btnImg);
if (!ret)
{
QString str = QString("图片 %1 加载失败").arg(btnImg);
qDebug() <<str;
return;
}
this->setFixedSize(pix.width(), pix.height());
this->setStyleSheet("QPushButton{border:0px}");
this->setIcon(pix);
this->setIconSize(QSize(pix.width(), pix.height()));
//初始化定时器对象
timer1 = new QTimer(this);
timer2 = new QTimer(this);
//监听正面翻反面(金币翻成银币)的信号,并且翻转金币
connect(timer1, &QTimer::timeout, [=](){
QPixmap pix;
QString str = QString(":/res/Coin000%1.png").arg(this->min++);
bool ret = pix.load(str);
if (!ret){
QString str = QString("图片 %1 加载失败").arg(str);
qDebug() <<str;
return;
}
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();
this->isAnimation = false;
}
});
//监听反面翻正面(银币翻成金币)的信号,并且翻转金币
connect(timer2, &QTimer::timeout, [=](){
QPixmap pix;
QString str = QString(":/res/Coin000%1.png").arg(this->max--);
bool ret = pix.load(str);
if (!ret){
QString str = QString("图片 %1 加载失败").arg(str);
qDebug() <<str;
return;
}
this->setFixedSize(pix.width(), pix.height());
this->setStyleSheet("QPushButton{border:0px}");
this->setIcon(pix);
this->setIconSize(QSize(pix.width(), pix.height()));
if (this->max < this->min)
{
this->max = 8;
timer2->stop();//翻转8次后才停止,显示整个翻转的过程
this->isAnimation = false;
}
});
}
//改变正反面的标志的方法
void MyCoin::changeFlag()
{
if (this->flag)
{
timer1->start(30);
this->flag = false;
this->isAnimation = true;//翻转开始,定时器停止翻转结束
}
else
{
timer2->start(30);
this->flag = true;
this->isAnimation = true;
}
}
//重写按下事件,只有在isAnimation为false时执行,避免翻转过程中连续点击按钮
//在执行中或胜利后不再执行点击事件
void MyCoin::mousePressEvent(QMouseEvent *e)
{
if (this->isAnimation || this->isWin)
{
return;
}
else
{
QPushButton::mousePressEvent(e);//交给父类处理
}
}
7、每个关卡的默认显示
1、先引入 dataconfig.h 和 dataconfig.cpp文件到项目中
2、在PlayScene中添加 int gameArray[4][4] 的数组,维护每个关卡的金币状态
3、初始化每个关卡的显示
8、单个金币翻转特效
1、给每个金币加属性:posX、posY、flag正反面标志
2、给MyCoin加函数changeFlag(),修改flag正反面标志
3、添加正向翻定时器timer1和反向翻定时器timer2,在点击金币后,如果flag为true改为false,并且开启timer1,开始正面翻反面,每张延时30ms显示,总显示8张,达到动画显示的效果;如果flag为false改为true,并且开启timer2,开始反面翻正面,每张延时30ms显示,总显示8张,达到动画显示的效果;
4、解决快速点击的效果不好问题,添加isAnimation,判断是否正在做动画,同时重写 mousePressEvent 事件,在 isAnimation = false时才响应,保证翻转的完整效果
9、翻转周围金币
点击金币,翻转玩击中的金币后,延时翻转周围上下左右的金币
10、判断胜利
1、在PlayScene中添加isWin的标志,来判断是否胜利;如果胜利了,屏蔽掉所有金币的点击
2、胜利图片特效显示,创建一个 winLabel 显示胜利的图片,游戏胜利时,将图片移动到屏幕中央
11、音效添加
1、QSound属于multimedia模块,需要在.pro文件中加入这个模块
QT += core gui multimedia
2、开始音效添加(开始场景 --- > 选择场景,选择场景 ---> 翻金币场景)
3、返回按钮音效添加(选择场景 ---> 开始场景 ,翻金币场景 ---> 选择场景)
4、翻金币音效添加
5、胜利音效添加
12、项目优化
1、三个场景切换的位置设置成一致,这样切换后场景位置不会变了
2、标志一定要赋初值,否则系统会随机初始化,导致金币翻不到
bool isAnimation = false;//执行动画标志,这里一定要赋初值!!!
bool isWin = false;//判断是否胜利的标志
3、在翻金币前,要先失能其他金币的鼠标点击事件,翻转完成后再使能其他金币的鼠标点击事件,防止快速点击,在翻转的过程中点击了其他金币
13、项目打包以及游戏扩展
1、选择 release,构建工程,生成 .exe文件
2、将 .exe文件拷贝到任意路径下,将其进行打包,这样在任意一台没有安装QT环境的电脑也可以运行,打包前首先要确保QT的安装目录下有 windeployqt.exe
3、打开windows的命令终端,进入到 .exe目录下(快捷方式:在文件夹中,点击空白处,shift+右键,选择“在此处打开窗口”),输入 windeployqt.exe [.exe文件名称]
4、生成成功后,会在当前目录下生成一些文件,将此文件夹拷贝到任意没有QT环境的电脑上也能运行
三、程序源码与图片资源路径
下面路径为本人的github路径,可以获取到整个工程源码,也可以私聊我获取资源。觉得有帮助的可以帮忙点个小星星啦
https://github.com/denghengli/qt_study/tree/master/16_CoinFlip