游戏首界面
窗口
创建一个MainScene
主体,继承自QMainWindow
,这里我们重写父类的一个方法void paintEvent(QPaintEvent *event);
在mainscene.cpp
中
// 绘图事件
void MainScene::paintEvent(QPaintEvent *event){
// 设置背景
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);
}
箭头指向的CoinFlip,就是Title.png
菜单栏的开始菜单,有个退出的菜单项,实现退出游戏
窗口标题,窗体标题的图标等等同样是在cpp中实现
connect(ui->actionQuit,&QAction::triggered,[=](){ // 退出
this->close();
});
this->setFixedSize(320,588);
this->setWindowTitle("翻金币");
this->setWindowIcon(QIcon(":/res/Coin0001.png"));
开始按钮的实现
自定义一个按钮类,是继承了
QPushButton
的类MyPushButton
,只需要用C++类文件创建
封装
设置按钮的形状
#ifndef MYPUSHBUTTON_H
#define MYPUSHBUTTON_H
// 这个类是用来封装我们自己设计的 按钮 类
#include <QDebug>
#include <QPushButton>
class MyPushButton : public QPushButton
{
Q_OBJECT
public:
QString normalImgPath; // 默认显示的图片
QString pressImgPath; // 按下后显示的图片
MyPushButton(QString normalImg,QString pressImg = "");
signals:
};
#endif // MYPUSHBUTTON_H
MyPushButton
类的构造函数,成员函数等都在这里mypushbutton.cpp
实现
#include "mypushbutton.h"
#include <QPropertyAnimation>
MyPushButton::MyPushButton(QString normalImg,QString pressImg){
// 保存图片路径
this->normalImgPath = normalImg;
this->pressImgPath = pressImg;
// 图片
QPixmap pix;
// 加载图片 不直接用绝对路径
bool ret = pix.load(this->normalImgPath);
if(!ret){
QString str = QString("%1 图片路径加载失败").arg(this->normalImgPath);
qDebug()<<str;
return ;
}
// 设定图片那位置的框的大小
this->setFixedSize(pix.width(),pix.height());
// 设置图片不规则样式 字符串内是用花括号!!!
this->setStyleSheet("QPushButton{border:0px;}");
// 设置图片
this->setIcon(pix);
// 设置放进框中的图片大小
this->setIconSize(QSize(pix.width(),pix.height()));
}
这里有一点需要注意
不管是类的构造函数还是普通函数,声明和实现时是不能够同时给参数缺省值的
设置按钮的特效
在MyPushButton.h
添加成员函数void up();
,void down();
的声明,同时需要头文件#include <QPropertyAnimation>
然后在mypushbutton.cpp
中进行实现
// 按钮向下跳跃的动画
void MyPushButton::down(){
// 动画 第一个参数是动画的对象 第二个是以什么形式
QPropertyAnimation * animation = new QPropertyAnimation(this,"geometry");
// 设置时间间隔
animation->setDuration(200);
// 设置动态对象的起始位置
animation->setStartValue(QRect(this->x(),this->y(),this->width(),this->height()));
// 设置结束位置
animation->setEndValue(QRect(this->x(),this->y()+10,this->width(),this->height()));
// 设置按钮弹跳效果 curve 曲线
animation->setEasingCurve(QEasingCurve::OutBounce);
// 开始
animation->start();
}
void MyPushButton::up(){
QPropertyAnimation * animation = new QPropertyAnimation(this,"geometry");
animation->setDuration(200);
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();
}
使用mypushbutton
类
在mainscene.cpp
中
// 创建按钮
// 第二个参数为空字符串 表示按下后没有变化为哪个图片
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,&QPushButton::clicked,this,[=](){
startBtn->down();
startBtn->up();
});
游戏第二个界面
点击开始后显示的页面,也就是选择关卡的界面
界面设置
这里Add New一个C++类文件,取名为ChooseLevelScene
,就不选用ui文件进行创建该界面,下面会用代码实现
chooselevelscene.h
#ifndef CHOOSELEVELSCENE_H
#define CHOOSELEVELSCENE_H
#include <QMainWindow>
class ChooseLevelScene : public QMainWindow
{
Q_OBJECT
public:
explicit ChooseLevelScene(QWidget *parent = nullptr);
// 设置第二界面的背景菜单大小等
void paintEvent(QPaintEvent *event);
signals:
};
#endif // CHOOSELEVELSCENE_H
chooselevelscene.cpp
#include "chooselevelscene.h"
#include<QMenuBar>
#include<QPainter>
ChooseLevelScene::ChooseLevelScene(QWidget *parent) : QMainWindow(parent)
{
this->setFixedSize(320,588);
this->setWindowTitle("翻金币");
this->setWindowIcon(QIcon(":/res/Coin0001.png"));
// 这里用代码进行创建菜单栏
QMenuBar * bar = menuBar();
this->setMenuBar(bar);
// 创建开始菜单
QMenu * startMenu = bar->addMenu("开始");
// 创建菜单项
QAction * quitAction = startMenu->addAction("退出");
connect(quitAction,&QAction::triggered,this,[=](){
this->close();
});
}
// 绘图事件
void ChooseLevelScene::paintEvent(QPaintEvent *event){
// 设置背景
QPainter painter(this);
QPixmap pix;
pix.load(":/res/PlayLevelSceneBg.png");
painter.drawPixmap(0,0,this->width(),this->height(),pix);
// 设置背景上的标题图片
pix.load(":/res/Title.png");
painter.drawPixmap((this->width()-pix.width())*0.5,30,pix.width(),pix.height(),pix);
}
接着在mainscene.cpp
中实例化一个第二界面的对象,通过点击开始按钮,隐藏首界面,展示第二界面
// 创建游戏的第二个界面
chooseScene = new ChooseLevelScene;
connect(startBtn,&QPushButton::clicked,this,[=](){
startBtn->down();
startBtn->up();
// 延时执行下面的代码 不然按钮的特效都看不见
// #include <QTimer>
QTimer::singleShot(500,this,[=](){
// 隐藏第一个界面
this->hide();
// 进入第二个界面
chooseScene->show();
});
});
返回按钮
这个按钮是在第二个界面的
// 返回按钮
MyPushButton * backBtn = new MyPushButton(":/res/BackButton.png",":/res/BackButtonSelected.png");
backBtn->setParent(this);
backBtn->move(this->width()-backBtn->width(),this->height()-backBtn->height());
这里给返回按钮也整一个特效,按下是一个样子,释放又是另一个样子
这两个事件Qt
也就有了,只需要在头文件声明,在源文件中重写
// 鼠标按下事件
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);
}
按下返回按钮,自然是返回第一个界面
chooselevelscene.cpp
// 点击返回按钮 返回到首页面
// 但有个问题 这里没法直接show第一个界面 因为第二界面是第一界面的成员变量而已
// 只有通过自定义的信号和槽 并在第一个界面实时监听
connect(backBtn,&QPushButton::clicked,[=](){
// 延时
QTimer::singleShot(100);
// 告诉上一层 这里返回了
emit this->chooseSceneBack();
});
而在第一个界面中进行监听
mainscene.cpp
// 监听第二个界面的返回信号
connect(chooseScene,&ChooseLevelScene::chooseSceneBack,[=](){
chooseScene->hide();
this->show();
});
别忘了在chooselevelscene.h
声明信号
signals:
// 自定义信号 只声明不实现
void chooseSceneBack();
关卡选择
chooselevelscene.cpp
// 选择关卡按钮
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);
// 设置关卡等级
// 这个不太好 效果可以自行看
// menuBtn->setText(i+1);
QLabel * label = new QLabel(this);
// 设置标签大小
label->setFixedSize(menuBtn->width(),menuBtn->height());
// int转QString
label->setText(QString::number(i+1));
label->move(25+(i%4)*70,130+(i/4)*70);
// 标签居中
label->setAlignment(Qt::AlignCenter);
// 因为标签在按钮上层 所以点击时标签优先响应
// 鼠标穿透 51号属性
label->setAttribute(Qt::WA_TransparentForMouseEvents);
// 测试
connect(menuBtn,&QPushButton::clicked,[=](){
QString str = QString("进入第 %1 关").arg(i+1);
qDebug()<<str;
});
}
![](https://i-blog.csdnimg.cn/blog_migrate/14f553984f24e954dbc93bc69053eaed.png)
新建C++类文件PlayScene
,因为在选择关卡,有20关,那构造函数肯定得传入一个整型参数的
playscene.h
修改构造函数
#ifndef PLAYSCENE_H
#define PLAYSCENE_H
#include <QMainWindow>
class PlayScene : public QMainWindow
{
Q_OBJECT
public:
// explicit PlayScene(QWidget *parent = nullptr);
PlayScene(int index);
signals:
};
#endif // PLAYSCENE_H
playscene.cpp
#include "playscene.h"
#include<QDebug>
PlayScene(int index){
QString str = QString("打开第%1关").arg(index);
qDebug()<<str;
}
在chooselevelscene.h
维护一个playscene
类的指针,再在chooselevelscene.cpp
的for
循环中实现进入关卡的功能
connect(menuBtn,&QPushButton::clicked,[=](){
this->hide();
//进入到具体的游戏场景
playScene = new PlayScene(i+1);
playScene->show();
});
第三个界面
关卡界面
点击选择,进入关卡后,大部分和前面两个常见一样,其余都是新的,左下角Level的显示,中心的金币阴影,其中返回按钮也稍有更新
![](https://i-blog.csdnimg.cn/blog_migrate/fa405ee196727239d9dbaa9ad204f17a.png)
同样的,在类中声明paintEvent
函数,再定义一个整型levelIndex
来记录关卡数,还有一个从第三个界面,关卡界面,返回到第二个界面的信号
int levelIndex;
// 重写绘图事件
void paintEvent(QPaintEvent * e);
signals:
void playSceneBack();
#include "playscene.h"
#include<QMenuBar>
#include<QPainter>
#include "mypushbutton.h"
#include <QLabel>
PlayScene::PlayScene(int index)
{
this->levelIndex = index;
this->setFixedSize(320,588);
this->setWindowTitle("翻金币");
this->setWindowIcon(QIcon(":/res/Coin0001.png"));
// 这里用代码进行创建菜单栏
QMenuBar * bar = menuBar();
this->setMenuBar(bar);
// 创建开始菜单
QMenu * startMenu = bar->addMenu("开始");
// 创建菜单项
QAction * quitAction = startMenu->addAction("退出");
connect(quitAction,&QAction::triggered,this,[=](){
this->close();
});
// 返回按钮
MyPushButton * backBtn = new MyPushButton(":/res/BackButton.png",":/res/BackButtonSelected.png");
backBtn->setParent(this);
backBtn->move(this->width()-backBtn->width(),this->height()-backBtn->height());
// 点击返回按钮 返回到首页面
// 但有个问题 这里没法show第一个界面 因为第二界面是第一界面的成员变量而已
// 只有通过自定义的信号和槽 并在第一个界面实时监听
connect(backBtn,&QPushButton::clicked,[=](){
// 告诉上一层 这里返回了
emit this->playSceneBack();
});
// 显示具体关卡号
QLabel * label = new QLabel(this);
QFont font;
font.setFamily("华文新魏");
// 字号
font.setPointSize(20);
label->setFont(font);
QString str = QString("Level: %1").arg(this->levelIndex);
label->setText(str);
// 设置位置
label->setGeometry(QRect(30,this->height()-50,120,50));
// 设置金币的背景阴影
for(int i=0;i<4;++i){
for(int j=0;j<4;++j){
// 用QLabel显示图片
QLabel * bg = new QLabel(this);
// bg->setGeometry(0,0,50,50); //这里的50 50不太好 这样写死了
// bg->setPixmap(QPixmap(":/res/BoardNode.png"));
// bg->move(57+i*50,200+j*50);
QPixmap pix;
pix.load(":/res/BoardNode.png");
bg->setGeometry(0,0,pix.width(),pix.height());
bg->setPixmap(pix);
bg->move(57+i*pix.width(),200+j*pix.height());
}
}
}
// 重写绘图事件
void PlayScene::paintEvent(QPaintEvent * e){
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);
}
Level显示,金币阴影,以及第三界面的整体画面都写好了,现在还剩返回按钮的信号和槽的链接
在chooselevelscene.cpp
中
connect(menuBtn,&QPushButton::clicked,[=](){
this->hide();
//进入到具体的游戏场景
playScene = new PlayScene(i+1);
playScene->show();
// 选择在这里进行链接 是因为每次new的playScene都不一样啊 放外面就不会有反应
// playScene 隐藏 选择关卡 显示
connect(playScene,&PlayScene::playSceneBack,[=](){
playScene->close(); // 这里就不是hide了 每次进入都是重新开始的吧
delete playScene; // 而且playScene是指针 不是类的对象
playScene = NULL;
this->show();
});
});