文章目录
主场景
设置游戏主场景配置
点击mainscene.ui文件,设计其菜单栏如下:
设计“退出”菜单项,objectName为 actionQuit, text 为 退出;
移除自带的工具栏与状态栏
回到MainScene.cpp文件,进入构造函数中,进行场景的基本配置;实现点击开始,退出游戏功能。代码如下:
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();
});
}
运行效果如图:
设置背景图片
在头文件声明:
//重写paintEvent事件 画背景图
void paintEvent(QPaintEvent *);
在源文件实现:
注意添加头文件’
#include <QPainter>
void MainScene::paintEvent(QPaintEvent *)
{
//创建画家,指定绘图设备
QPainter painter(this);
//创建QPixmap对象
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);
}
运行效果如图:
创建开始按钮
开始按钮点击后有弹跳效果,这个效果是我们利用自定义控件实现的(QPushButton不会自带这类特效),我们可以自己封装出一个按钮控件,来实现这些效果。
创建MyPushButton,继承与QPushButton
点击完成。
修改MyPushButton的父类
提供MyPushButton的构造的重载版本,可以让MyPushButton提供正常显示的图片以及按下后显示的图片.代码如下:
class MyPushButton : public QPushButton
{
Q_OBJECT
public:
// explicit MyPushButton(QWidget *parent = nullptr);
//normalImg 代表正常显示的图片
//pressImg 代表按下后显示的图片,默认为空
//构造函数 参数1 正常显示的图片路径 参数2 按下后显示的图片路径
MyPushButton(QString normalImg,QString pressImg="");
//成员属性 保存用户传入的默认显示路径 以及按下后显示的图片路径
QString normalImgPath;
QString pressImgPath;
signals:
};
实现的重载版本MyPushButton构造函数代码如下:
MyPushButton::MyPushButton(QString normalImg,QString pressImg)
{
this->normalImgPath=normalImg;
this->pressImgPath=pressImg;
//判断是否能够加载正常显示的图片,若不能提示加载失败
QPixmap pix;
bool ret=pix.load(normalImg);
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()));
}
回到MainScene的构造函数中,创建开始按钮
//开始按钮
MyPushButton * startBtn = new MyPushButton(":/res/MenuSceneStartButton.png");
startBtn->setParent(this);
startBtn->move(this->width()*0.5-startBtn->width()*0.5,this->height()*0.7);
运行效果如图:
开始按钮跳跃特效实现
在mypushbutton.h头文件,添加两个变量
//弹跳特效
void zoom1();//向下跳
void zoom2();//向上跳
连接信号槽,监听开始按钮点击.在mymainscene.cpp代码如下:
引入头文件
#include "mypushbutton.h"
//监听点击事件,执行特效
connect(startBtn,&MyPushButton::clicked,[=](){
qDebug()<<"点击了开始";
//做弹起特效
//startBtn->zoom1(); //向下跳跃
startBtn->zoom2(); //向上跳跃
zoom1与zoom2 为MyPushButton中扩展的特效代码,mypushbutton.cpp具体如下:
引入头文件
#include<QPropertyAnimation>
void MyPushButton::zoom1()
{
//创建动画对象
QPropertyAnimation * animation1 = new QPropertyAnimation(this,"geometry");
//设置时间间隔,单位毫秒
animation1->setDuration(200);
//创建起始位置
animation1->setStartValue(QRect(this->x(),this->y(),this->width(),this->height()));
//创建结束位置
animation1->setEndValue(QRect(this->x(),this->y()+10,this->width(),this->height()));
//设置缓和曲线,QEasingCurve::OutBounce 为弹跳效果 animation1->setEasingCurve(QEasingCurve::OutBounce);
//开始执行动画
animation1->start();
}
void MyPushButton::zoom2()
{
QPropertyAnimation * animation1 = new QPropertyAnimation(this,"geometry");
animation1->setDuration(200);
animation1->setStartValue(QRect(this->x(),this->y()+10,this->width(),this->height()));
animation1->setEndValue(QRect(this->x(),this->y(),this->width(),this->height()));>setEasingCurve(QEasingCurve::OutBounce);
animation1->start();
}
创建选择关卡场景
点击开始按钮后,进入选择关卡场景。
首先我们先创建选择关卡场景,添加新的C++文件
类名为ChooseLevelScene 选择基类为QMainWindow,点击下一步,然后点击完成。
点击开始按钮进入选择关卡场景
目前点击主场景的开始按钮,只有弹跳特效,但是我们还需要有功能上的实现,特效结束后,我们应该进入选择关卡场景
在MainScene.h中 保存ChooseScene选择关卡场景对象
//选择关卡场景
ChooseLevelScene *chooseScene = new ChooseLevelScene;
我们在zoom1和zoom2特效后,延时0.5秒,进入选择关卡场景,代码如下:
//延时0.5秒后 进入选择场景
QTimer::singleShot(500, this,[=](){
this->hide();
chooseScene->show();
});
选择关卡场景
场景基本设置
选择关卡构造函数如下:
//设置窗口固定大小
this->setFixedSize(320,588);
//设置图标
this->setWindowIcon(QPixmap(":/res/Coin0001.png"));
//设置标题
this->setWindowTitle("选择关卡");
//创建菜单栏
QMenuBar * bar = this->menuBar();
this->setMenuBar(bar);
//创建开始菜单
QMenu * startMenu = bar->addMenu("开始");
//创建按钮菜单项
QAction * quitAction = startMenu->addAction("退出");
//点击退出 退出游戏
connect(quitAction,&QAction::triggered,[=](){
this->close();});
运行效果如图:
背景设置
头文件chooselevelscene.h:
//重写绘图事件
void paintEvent(QPaintEvent *);
源文件chooselevelscene.cpp,创建返回按钮:
//返回按钮
MyPushButton * closeBtn = new MyPushButton(":/res/BackButton.png",":/res/BackButtonSelected.png");
closeBtn->setParent(this);
closeBtn->move(this->width()-closeBtn->width(),this->height()-closeBtn->height());
返回按钮是有正常显示图片和点击后显示图片的两种模式,所以我们需要重写MyPushButton中的 MousePressEvent和MouseReleaseEvent。
在mypushbutton.h中函数声明,在mypushbutton.cpp中实现
//鼠标事件
void MyPushButton::mousePressEvent(QMouseEvent *e)
{
if(pressedImgPath != "") //选中路径不为空,显示选中图片
{
QPixmap pixmap;
bool ret = pixmap.load(pressedImgPath);
if(!ret)
{
qDebug() << pressedImgPath << "加载图片失败!";
}
this->setFixedSize( pixmap.width(), pixmap.height() );
this->setStyleSheet("QPushButton{border:0px;}");
this->setIcon(pixmap);
this->setIconSize(QSize(pixmap.width(),pixmap.height()));
}
//交给父类执行按下事件
return QPushButton::mousePressEvent(e);
}
void MyPushButton::mouseReleaseEvent(QMouseEvent *e)
{
if(normalImgPath != "") //选中路径不为空,显示选中图片
{
QPixmap pixmap;
bool ret = pixmap.load(normalImgPath);
if(!ret)
{
qDebug() << normalImgPath << "加载图片失败!";
}
this->setFixedSize( pixmap.width(), pixmap.height() );
this->setStyleSheet("QPushButton{border:0px;}");
this->setIcon(pixmap);
this->setIconSize(QSize(pixmap.width(),pixmap.height()));
}
//交给父类执行 释放事件
return QPushButton::mouseReleaseEvent(e);
}
返回按钮
在这里我们点击返回后,延时0.5后隐藏自身,并且发送自定义信号,告诉外界自身已经选择了返回按钮。
在chooselevelscene.h中定义信号量
signals:
//写一个自定义信号,告诉主场景 点击了返回
void chooseSceneBack();
//返回按钮功能实现
connect(closeBtn,&MyPushButton::clicked,[=](){
QTimer::singleShot(500, this,[=](){
this->hide();
//触发自定义信号,关闭自身,该信号写到 signals下做声明
emit this->chooseSceneBack();
}
);
});
在主场景MainScene中 点击开始按钮显示选择关卡的同时,监听选择关卡的返回按钮消息.在mainsece.cpp中:
//监听选择场景的返回按钮 connect(chooseScene,&ChooseLevelScene::chooseSceneBack,[=](){
chooseScene ->hide();
this->show();
});
创建选择关卡按钮
//创建关卡按钮
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);
//监听每个按钮的点击事件
connect(menuBtn,&MyPushButton::clicked,[=](){
QString str=QString("您选择的是第%1关").arg(i+1);
qDebug()<<str;
});
//按钮上显示的文字
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上的文字对齐方式 水平居中和垂直居中
label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
//设置鼠标事件穿透 51号属性
label->setAttribute(Qt::WA_TransparentForMouseEvents,true);
}
运行效果如果:
创建翻金币场景
点击关卡按钮后,会进入游戏的核心场景,也就是翻金币的场景,首先先创建出该场景的.h和.cpp文件
创建PlayScene
点击选择关卡按钮后会跳入到该场景
建立点击按钮,跳转场景的信号槽连接
在chooselevelscene.h中:
添加
#include"playscene.h"
//游戏场景对象指针
PlayScene * play =NULL;
在chooselevel.cp中:
//监听每个按钮的点击事件
connect(menuBtn,&MyPushButton::clicked,[=](){
QString str=QString("您选择的是第%1关").arg(i+1);
qDebug()<<str;
//进入到游戏场景
this->hide(); //将选关场景隐藏掉
play = new PlayScene(i+1); //创建游戏场景
play->show(); //显示游戏场景
});
场景基本设置
在PlayScene.h中 声明成员变量,用于记录当前用户选择的关卡
int levelIndex; //内部成员属性 记录所选的关卡
PlayScene.cpp中 初始化该场景配置
PlayScene::PlayScene(int levelNum)
{
QString str=QString("进入了第%1关").arg(levelNum);
qDebug()<<str;
this->levelIndex=levelNum;
//设置窗口固定大小
this->setFixedSize(320,588);
//设置图标
this->setWindowIcon(QPixmap(":/res/Coin0001.png"));
//设置标题
this->setWindowTitle("翻金币");
//创建菜单栏
QMenuBar * bar = this->menuBar();
this->setMenuBar(bar);
//创建开始菜单
QMenu * startMenu = bar->addMenu("开始");
//创建退出 菜单项
QAction * quitAction = startMenu->addAction("退出");
//点击退出 退出游戏
connect(quitAction,&QAction::triggered,[=](){
this->close();});
}
背景设置
void PlayScene::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.width(),pix.height(),pix);
}
返回按钮
//返回按钮
MyPushButton * closeBtn = new MyPushButton(":/res/BackButton.png",":/res/BackButtonSelected.png");
closeBtn->setParent(this);
closeBtn->move(this->width()-closeBtn->width(),this->height()-closeBtn->height());
//返回按钮功能实现
connect(closeBtn,&MyPushButton::clicked,[=](){
qDebug()<<"点击了返回按钮";
QTimer::singleShot(500, this,[=](){
this->hide();
//触发自定义信号,关闭自身,该信号写到 signals下做声明
emit this->chooseSceneBack();
});
});
在ChooseScene选择关卡场景中,监听PlayScene的返回信号
connect(play,&PlayScene::chooseSceneBack,[=](){
this->show();
delete play;
play = NULL;
});
//当前关卡标题
QLabel * label = new QLabel;
label->setParent(this);
QFont font;
font.setFamily("华文新魏");
font.setPointSize(20);
label->setFont(font);
QString str1 = QString("Leavel: %1").arg(this->levelIndex);
//将字体设置到标签控件中
label->setText(str1);
label->setGeometry(QRect(30, this->height() - 50,120, 50)); //设置大小和位置
假设我们选择了第15关卡,运行效果如果:
创建金币背景图片
//创建金币的背景图片
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(57 + i*50,200+j*50);
}
}
创建金币类
金币是本游戏的核心对象,并且在游戏中可以利用二维数组进行维护,拥有支持点击,翻转特效等特殊性,因此不妨将金币单独封装到一个类中,完成金币所需的所有功能。
创建金币类 MyCoin
并修改MyCoin的基类为QPushButton
构造函数
在资源图片中,我们可以看到,金币翻转的效果原理是多张图片切换而形成的,而以下八张图片中,第一张与最后一张比较特殊,因此我们在给用户看的时候,无非是金币Coin0001或者是银币 Coin0008这两种图。
因此我们在创建一个金币对象时候,应该提供一个参数,代表着传入的是金币资源路径还是银币资源路径,根据路径我们创建不同样式的图案。
在MyCoin.h中声明:
//参数代表图片路径
MyCoin(QString butImg);
在MyCoin.cpp中进行实现
MyCoin::MyCoin(QString butImg)
{
QPixmap pixmap;
bool ret = pixmap.load(butImg);
if(!ret)
{
qDebug() << butImg << "加载图片失败!";
}
this->setFixedSize( pixmap.width(), pixmap.height() );
this->setStyleSheet("QPushButton{border:0px;}");
this->setIcon(pixmap);
this->setIconSize(QSize(pixmap.width(),pixmap.height()));
}
测试
在翻金币场景 PlayScene中,我们测试下封装的金币类是否可用,可以在创建好的金币背景代码后,添加如下代码:
//金币
MyCoin * coin = new MyCoin(":/res/Coin0001.png");
coin->setParent(this);
coin->move(59 + i*50,204+j*50);
效果如图:
引入关卡数据
我们引用一个现有的关卡文件,文件中记录了各个关卡的金币排列清空,也就是二维数组的数值。
添加现有文件dataConfig
首先先将dataConfig.h 和 dataConfig.cpp文件放入到当前项目下:
添加现有文件
其次在Qt_Creator项目右键,点击添加现有文件
完成添加
选择当前项目下的文件,并进行添加
测试关卡数据
在Main函数可以测试第一关的数据,添加如下代码:
dataConfig config;
for(int i = 0 ; i < 4;i++)
{
for(int j = 0 ; j < 4; j++)
{
//打印第一关所有信息
qDebug() << config.mData[1][i][j];
}
qDebug()<< "";
}
初始化各个关卡
首先,可以在playScene中声明一个成员变量,用户记录当前关卡的二维数组
int gameArray[4][4]; //二维数组数据
之后,在.cpp文件中,初始化这个二维数组
//初始化二维数组
dataConfig config;
for(int i = 0 ; i < 4;i++)
{
for(int j = 0 ; j < 4; j++)
{
gameArray[i][j] = config.mData[this->levalIndex][i][j];
}
}
在my.coin完成变量声明
//金币的属性
int posX;//x坐标位置
int posY;//y坐标位置
bool flag;//正负标识
完成金币初始化,代码如下:
//创建金币
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(59 + i*50,204+j*50);
//给金币属性赋值
coin->posX = i; //记录x坐标
coin->posY = j; //记录y坐标
coin->flag =this->gameArray[i][j]; //记录正反标志
添加金币特效
在mycoin.h中添加属性
//改变标识的方法
void changeFlag();
QTimer timer1; //正面翻反面的定时器
QTimer timer2; //反面翻正面的定时器
int min=1;
int max=8;
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 pixmap;
QString str = QString(":/res/Coin000%1.png").arg(this->min++);
pixmap.load(str);
this->setFixedSize(pixmap.width(),pixmap.height() );
this->setStyleSheet("QPushButton{border:0px;}");
this->setIcon(pixmap);
this->setIconSize(QSize(pixmap.width(),pixmap.height()));
//如果大于最大值,重置最小值,并停止定时器
if(this->min > this->max)
{
this->min = 1;
timer1->stop();
}
});
//监听反面翻正面的信号,并翻转金币
connect(timer2,&QTimer::timeout,[=](){
QPixmap pixmap;
QString str = QString(":/res/Coin000%1.png").arg(this->min++);
pixmap.load(str);
this->setFixedSize(pixmap.width(),pixmap.height() );
this->setStyleSheet("QPushButton{border:0px;}");
this->setIcon(pixmap);
this->setIconSize(QSize(pixmap.width(),pixmap.height()));
//如果大于最大值,重置最小值,并停止定时器
if(this->min > this->max)
{
this->min = 1;
timer2->stop();
}
});
测试
监听每个按钮的点击效果,并翻转金币
connect(coin,&MyCoin::clicked,[=](){
//qDebug() << "点击的位置: x = " << coin->posX << " y = " << coin->posY ;
coin->changeFlag();
gameArray[i][j] = gameArray[i][j] == 0 ? 1 : 0; //数组内部记录的标志同步修改
});
优化特效
此时,确实已经可以执行翻转金币代码了,但是如果快速点击,会在金币还没有执行一个完整动作之后 ,又继续开始新的动画,我们应该在金币做动画期间,禁止再次点击,并在完成动画后,开启点击。
在MyCoin类中加入一个标志 isAnimation 代表是否正在做翻转动画,默认isAnimation值为false。
//执行动画 标志
bool isAnimation = false;
在MyCoin做动画期间加入
this->isAnimation = true;
并且在做完动画时,将标志改为false
重写按钮的按下事件,判断如果正在执行动画,那么直接return掉,不要执行后续代码。
在mycoin.h中声明
//重写 按下
void mousePressEvent(QMouseEvent *e);
源文件代码如下:
void MyCoin::mousePressEvent(QMouseEvent *e)
{
if(this->isAnimation)
{
return;
}
else
{
QPushButton::mousePressEvent(e);
}
}
当然在构造函数中,记得创建出两个定时器
//初始化定时器
timer1 = new QTimer(this);
timer2 = new QTimer(this);
创建特效
当我们分别启动两个定时器时,需要在构造函数中做监听操作,并且做出响应,翻转金币,然后再结束定时器。
构造函数中 进行下列监听代码:
//监听正面翻转的信号槽
connect(timer1,&QTimer::timeout,[=](){
QPixmap pixmap;
QString str = QString(":/res/Coin000%1.png").arg(this->min++);
pixmap.load(str);
this->setFixedSize(pixmap.width(),pixmap.height() );
this->setStyleSheet("QPushButton{border:0px;}");
this->setIcon(pixmap);
this->setIconSize(QSize(pixmap.width(),pixmap.height()));
//如果大于最大值,重置最小值,并停止定时器
if(this->min > this->max)
{
this->min = 1;
isAnimation=false;
timer1->stop();
}
});
//监听反面翻正面的信号,并翻转金币
connect(timer2,&QTimer::timeout,[=](){
QPixmap pixmap;
QString str = QString(":/res/Coin000%1.png").arg((this->max)-- );
pixmap.load(str);
this->setFixedSize(pixmap.width(),pixmap.height() );
this->setStyleSheet("QPushButton{border:0px;}");
this->setIcon(pixmap);
this->setIconSize(QSize(pixmap.width(),pixmap.height()));
//如果大于最大值,重置最小值,并停止定时器
if(this->max < this->min)
{
this->max = 8;
isAnimation=false;
timer2->stop();
}
});
在playscene.cpp中,监听每个按钮的点击效果,并翻转金币
//点击金币 进行翻转
connect(coin,&MyCoin::clicked,[=](){
//qDebug() << "点击的位置: x = " << coin->posX << " y = " << coin->posY ;
coin->changeFlag();
gameArray[i][j] = gameArray[i][j] == 0 ? 1 : 0; //数组内部记录的标志同步修改
禁用按钮
在MyCoin.h,playscene.h中加入 isWin标志,代表是否胜利。
bool isWin = false; //是否胜利
默认设置为true,只要有一个反面的金币,就将该值改为false,视为未成功。
在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)
{
qDebug()<<"游戏胜利";
//将所有按钮的胜利标志改为true;如果再次点击按钮,直接return掉
for(int i=0;i<4;i++)
{
for(int j=0;j<4;j++)
{
coinBtn[i][j]=>isWin=true;
}
}
在mycoin中,当所有按钮为isWin为true时候,全部return 掉
优化
防止点击一个按钮后迅速点击另一个。
打开禁用:
//点击按钮 将所有按钮都先禁用
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;
}
}