Qt简单迷宫游戏
目录
- 你将学到
- 你将准备
- 你将改变
- 你将设计
- 你将编程
- 开始界面
- 游玩界面
- 胜利界面
- 其它bug修复
- 你可扩展
- 下一篇博客要说的东西
你将学到
Qt
中QKeySequence
对象的基本创建Qt
中QShortcut
对象的基本应用Qt
中QSoundEffect
对象的基本应用
你将准备
在开始制作Qt简单迷宫游戏之前,我们先要准备这一些东西。
-
这样,一个关于Qt简单迷宫游戏的项目就正式构建完成了。
你将改变
之后,我们我将要改变一些东西在这个游戏的项目里。
在你做改好这一些东西之后,你的Qt简单迷宫游戏就可以发出音效并使用图片了。
你将设计
为了完成这个迷宫游戏,我们也要设计一下UI,才能让用户看着舒服。
之后,你的迷宫游戏所有界面的UI的基本框架也就构建完成了。
你将编程
开始界面
在编程之前,如果没有一个做这个迷宫游戏的方法或策略的话,那么对于一个普通人来说,小型的项目大多数可能还是很快就可以能想到怎么做的,而大型的项目呢,则根本做不了,简直无从下手!就像打怪一样,弱的怪,你轻轻松松就能打败;而强的怪呢,即使你有实力,但你如果没有策略的话,就很难打败他了,甚至你会因此而失败!因此,我们在做这个迷宫游戏,乃至其他的东西的时候,最好要制定一个方法或策略来做这个东西。看到这,你或许就可以想一下这游戏到底要怎么做了,当然,你也可以看下下面的建议方法。
在上面,你可以知道,首先要实现开始界面,就要为开始界面里的一些UI实现一些用于开始的功能,那么要怎样开始呢?很简单,只需要把这个迷宫游戏的UI里的stackwidget
的当前索引设为1就行了,当然,为了使游戏的开始界面更美观,我们可以把资源里的图片设置于当标题图片用的QLabel
标签label
和用于当开始按钮的QToolButton
的Icon
,并使菜单栏中刚才唯一设置过的名为kaishi1
动作的名字设为开始
,就好了,现在的MainWindow
的构造方法代码及流程图如下。
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QPixmap pm(":/start.png");
QPixmap pma(":/title.png");
ui->label->setPixmap(pma);
ui->toolButton->setStyleSheet("QToolButton{border:0px;}");
ui->toolButton->setIconSize(QSize(200,200));
ui->toolButton->setIcon(pm);
connect(ui->toolButton, &QToolButton::clicked, [=](){
ui->stackedWidget->setCurrentIndex(1);
});
connect(ui->actionkaishi1, &QAction::triggered, [=](){
ui->stackedWidget->setCurrentIndex(1);
});
ui->actionkaishi1->setText("开始");
}
在开始界面实现好之后,我们可以来测试一下这个开始界面有没有bug。测试好之后,如果你发现了stackwidget
当前跳转的页不对,那么请转到刚才的设计界面,右键选中stackWidget
,并点击改变页顺序,在这里,你可以修正这个stackwidget
当前跳转的页不对”的bug,具体实现步骤如下。
修正好这个bug之后,我们这个Qt简单迷宫游戏也就完成了 1 3 \frac{1}{3} 31了,接下来,我们就正式实现Qt简单迷宫游戏开始之后的游玩界面了,准备好了吗?
游玩界面
在实现这个游戏界面之前,我们需要一个迷宫,才能实现这个迷宫游戏的游玩界面,那么,迷宫哪里去找呢?很简单,要么上网去查迷宫,然后加以改进,要么自己造个迷宫,我这里由于迷宫比较难找,所以,就自己造了个迷宫。
造好后,我们就要根据这个迷宫及迷宫的大小定义一个有20行20列的二维字符数组strmaze
,当然,你也可以按这样的方式定义一个独属于你自己的strmaze
。之后,我们就要根据建议方法中的“走”字,来制定一套玩家行走的逻辑了,如果玩家不走的话,那游戏就无聊了,别人肯定跟你一样也不想玩,那么,普通的迷宫大家都应该了解了吧,就是有墙不能走,走到出口就胜利,并且能走四个方向,因此,即使没了解过迷宫的读者也不用担心,因为我们在这里抽象成了一个逻辑,能更好的为你知道迷宫该怎样走(迷宫的边界也可以算作墙)。
那么,知道这个迷宫游戏中玩家走的逻辑之后,我们就先要显示出这个迷宫了,只有这样,玩家才能更好的玩上这个迷宫(我不推荐玩家盲走,因为玩家会觉得游戏太难,但随你便),那要怎样显示出这个迷宫呢?很简单,我们只需要先按刚才造好的迷宫的字符一一按照对应的图片及格式添加进窗口里面,然后再添加玩家朝着某个方向的图片(我这里选玩家朝右的图片)在窗口的左上角,就好了,那么,如果我们要创建这个标签并给标签设置图片的话,就需要一个临时的标签变量来调整这个标签,而之后,由于我们还要操作这个用了玩家图片的标签,所以,我们还需要一个临时的标签变量来存储这个标签。由此,你大概可以想到这个代码应该是怎样的了。但不管你想到的代码是怎么样,有bug的,没bug的,很短的……反正如果没有封装过这些代码的话,后面开发的时候重复部分越来越臭长,让人看得越来越红温。所以,我们还要通过一个成员方法来完成开始游戏的操作。
//一般在mainwindow.h里面的Mainwindow类里
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void startgame();
private:
Ui::MainWindow *ui;
};
//一般要在mainwindow.cpp里面添加的方法
void MainWindow::startgame(){//函数名不能用“start”,会有二义性
ui->stackedWidget->setCurrentIndex(1);
}
同时,我们也可以改变一下MainWindow
构造方法中的某些信号所执行的槽函数。
connect(ui->toolButton, &QToolButton::clicked, [&](){
this->startgame();
});
connect(ui->actionkaishi1, &QAction::triggered, [&](){
this->startgame();
});
之后,就可以往startgame
函数里面添加你刚才所想的代码了,注意要引上QLabel
类文件,下面的流程图能更好地理解下面代码的意思。
ui->stackedWidget->setCurrentIndex(1);
char strmaze[20][20] = {
'P',' ',' ','*',' ',' ','*',' ','*',' ',' ',' ',' ','*',' ',' ',' ',' ',' ','*',
' ',' ',' ',' ',' ','*',' ','*',' ',' ',' ',' ','*',' ',' ',' ',' ',' ',' ','*',
' ',' ',' ','*',' ','*',' ',' ',' ',' ',' ','*',' ',' ',' ','*',' ',' ','*','*',
' ','*','*',' ',' ',' ','*','*',' ','*',' ',' ',' ',' ','*',' ',' ',' ',' ',' ',
' ',' ','*',' ',' ',' ','*','*',' ','*',' ','*',' ','*',' ',' ',' ','*',' ',' ',
' ',' ','*',' ','*',' ',' ','*',' ',' ','*',' ','*',' ',' ',' ',' ','*',' ','*',
' ','*','*',' ',' ','*',' ','*',' ',' ',' ',' ',' ','*',' ',' ','*',' ',' ',' ',
' ',' ','*','*',' ',' ','*','*','*','*','*',' ',' ',' ','*',' ',' ',' ','*',' ',
'*',' ','*',' ','*',' ',' ',' ',' ',' ',' ','*','*',' ',' ','*','*','*',' ',' ',
' ',' ','*',' ',' ','*',' ','*','*','*',' ',' ',' ',' ','*',' ',' ',' ',' ',' ',
' ','*','*',' ',' ',' ',' ','*',' ',' ','*','*','*',' ','*',' ',' ','*','*','*',
' ',' ','*',' ',' ','*',' ','*',' ','*',' ',' ','*',' ','*',' ',' ',' ','*',' ',
'*',' ','*',' ','*',' ','*','*','*',' ',' ','*',' ',' ','*',' ',' ',' ','*',' ',
' ','*','*',' ',' ',' ',' ',' ',' ',' ',' ',' ','*',' ',' ','*','*',' ','*',' ',
' ','*',' ','*','*','*',' ',' ','*',' ',' ','*',' ',' ',' ','*',' ',' ',' ',' ',
' ',' ',' ',' ',' ',' ',' ',' ',' ','*',' ',' ','*',' ',' ','*',' ','*',' ',' ',
' ',' ','*',' ','*',' ','*','*','*',' ',' ','*',' ',' ',' ','*',' ','*','*',' ',
' ',' ','*','*','*',' ',' ','*',' ',' ',' ',' ','*',' ',' ','*',' ','*','*','*',
' ',' ','*',' ','*',' ','*','*','*',' ',' ','*',' ',' ',' ','*',' ',' ','*',' ',
' ',' ',' ',' ',' ',' ',' ',' ',' ','*',' ',' ','*',' ',' ','*','*',' ',' ','G',
};
QLabel* lb = new QLabel(ui->page_2);
QPixmap pm(48, 48);
for (int i = 0; i <= 400; i++){
lb = new QLabel(ui->page_2);
if (400 == i){
lb->move(0, 0);
pm.load(":/Playerd.png");
}
else {
lb->move(i % 20 * 48, i / 20 * 48);
switch(strmaze[i / 20][i % 20]){
case '*':
pm.load(":/wall.png");
break;
case 'G':
pm.load(":/Goal.png");
break;
default:
pm.fill(Qt::white);
break;
}
}
pm = pm.scaled(48, 48);
lb->setPixmap(pm);
lb->show();
}
在做好显示迷宫的代码后,我们就要对这个玩家标签lb
进行移动操作了。移动,肯定是要有个操作方式来执行的,比如大家最广为人知的键盘,又比如PushButton
,ToolButton
……而这次,我们要用键盘来实现移动的功能。首先,我们要想一种既得心应手又畅通无阻而且可以接收键盘按键按下的信号的东西,KeyPressEvent
事件可行吗?不可行,可能会使程序卡死。那——除了KeyPressEvent
事件,没有什么可以接收键盘按键按下的信号啊。欸——其实有一个东西既能得心应手,又畅通无阻,而且可以接收键盘按键按下的信号,那就是:QShortcut
快捷键!
那么,我们既然知道了QShortcut
快捷键这一个东西,就应该要知道QShortcut
快捷键的用途了,QShortcut
快捷键,顾名思义,主要优点就是很快捷,而“键”字,就说明他跟键盘有关系,能触发有关键盘的信号,那,QShortcut
要怎样创建呢?其实只要先导入QShortcut
类文件,然后Qt
的人性系统就让我们知道了它的其中一种构造方式:之后,我们可以知道,在这个重载的构造方法中,首先需要一个QKeySequence
对象,因此,我们就在QShortcut
构造方法的第一个参数中填入一个匿名的QKeySequence
对象来,那这个QKeySequence
对象是用来干什么的呢?其实翻译一下KeySequence
这个单词,就可以知道它是用来存键的序列的,“序列”这个词,第一次看有点不熟悉,但只要加以查找一下它的意思,可能还有点陌生,回过头来,只要联想一下快捷键的功能,就可以知道它是用来存一个键或是几个键的组合的,是每个快捷键都必须拥有的核心,因此,根据刚才想到的,我们把你想要的要按下的键想成字符串,就可以定义一个QKeySequence
对象了。现在,第一个参数QKeySequence("W")
就出来了。然后,第二个参数需要一个parent
对象,根据前面的理解,只需要万能的this
指针就好。最后,还需要为这个快捷键有一个移动的方法,就需要便携的匿名槽函数来助力了。三个参数都有了,那QShortcut
对象也就可以创建完成了:
new QShortcut(QKeySequence("W"), this, [=](){
//移动操作
});
接下来,我们还要另外创建3个QShortcut
对象用来使玩家朝其他的方向移动,不过,这里可以用数组来存这四个已经创建好的QShortcut
对象和另外创建3个QShortcut
对象:
//在mainwindow.cpp的startgame方法里
QShortcut* movesc[4]={
new QShortcut(QKeySequence("W"), this, [=](){
//上移操作
}),
new QShortcut(QKeySequence("A"), this, [=](){
//左移操作
}),
new QShortcut(QKeySequence("S"), this, [=](){
//下移操作
}),
new QShortcut(QKeySequence("D"), this, [=](){
//右移操作
})
};
创建好后,我们就得往这一些匿名槽函数添加点实现进去了,首先完成右移操作。要想使玩家右移,可以直接移动,也可以平滑移动,当然,为了追求用户的体验,比较合适的是平滑移动,平滑移动的话,就需要一个动画来让玩家进行平滑移动了,要想平滑移动,就用QPropertyAnimation
动画对象来进行移动很合适。首先,要先导入QPropertyAnimation
类文件,然后,在定义4个QShotrcut
对象之前,new
一个QPropertyAnimation
对象,接着设置动画曲线,开始坐标,结束坐标,之后start
方法一写,基本的平滑移动的方式就大功告成了,接着,如果需要改变玩家标签lb
的朝向,那setPixmap
一下就行了,在这之前,要设置的图片可以按你的想法来设置,只要让用户觉得没问题就行。在这两步操作之后,你右移操作的代码应该就是这样子了:
new QShortcut(QKeySequence("D"), this, [=](){
pm.load(":/Playerd.png");
pm = pm.scaled(48, 48);
lb->setPixmap(pm);
pa->setEasingCurve(QEasingCurve::Linear);
pa->setStartValue(QRect(lb->x(), lb->y(), this->width(), this->height()));
pa->setEndValue(QRect(lb->x() + 48, lb->y(), this->width(), this->height()));
pa->start();
})
但,这有个问题,就是QPixmap
图片pmPlayer
变成const
常量了,从而导致pmPlayer
把图片缩成大小为48x48的图片时出现了问题,并且pmPlayer
也无法加载图片了。“这还不简单?”你想,“我让QPixmap
图片pmPlayer
在这个槽函数里设为引用传递,就行了。这不轻而易举吗!”你想的的确是好,但又想得太好了,按你这样做的话,最终的结果应该就是这样子——
由此可知,这个槽函数还是有bug,那么,我们如何让槽函数里的QPixmap
图片pmPlayer
既不为const
常量,又不让它为空图片呢,不难,如果单从QPixmap
图片pmPlayer
来想的话,那这个问题就困难多了,而直接在槽函数里另外创建一个QPixmap
图片,远比刚才简单多了,虽然会占内存空间,但好在他简单,所以我们直接就用这个办法就行了:
new QShortcut(QKeySequence("D"), this, [=](){
QPixmap pmPlayer(":/Playerd.png");
pmPlayer = pmPlayer.scaled(48, 48);
lb->setPixmap(pmPlayer);
pa->setEasingCurve(QEasingCurve::Linear);
pa->setStartValue(QRect(lb->x(), lb->y(), this->width(), this->height()));
pa->setEndValue(QRect(lb->x() + 48, lb->y(), this->width(), this->height()));
pa->start();
})
然后,我们测试之后,会看到玩家在向右移动的时候往下偏移了非常多格:所以,就要让玩家标签lb
在第一次移动前再往上偏移几百像素,就好了,想知道具体偏移了多少就来测试一下吧,这是我的修正偏移的方法:
if (this->bmove){//有this指针,就有成员函数在mainwindow.h里面
lb->move(0, -276);
this->bmove = false;
}
之后,这个玩家标签lb
就能正常的移动了,而接下来,我们只要往里面实现玩家移动的逻辑和如何判断玩家胜利的逻辑,整个游玩界面也就到此结束了。从移动的逻辑来,移动的逻辑,是整个迷宫游戏中最重要的逻辑,具体的逻辑可见上面的“游玩界面”标题旁的图。实现起来也比较简单,只要一个判断条件,一个指针,就行,但是,还有一步,也是最重要的一步,就是——把二维字符数组strmaze
变成静态的二维字符数组,因为在这些逻辑之后,start
成员方法就结束了,使没有静态化的strmaze
就被销毁了,没有了这个strmaze
,也就无法正常的运行了,之后,就可以正式地开始写右移的逻辑了。
new QShortcut(QKeySequence("D"), this, [=](){
QPixmap pmPlayer(":/Playerd.png");//玩家移动时的朝向
pmPlayer = pmPlayer.scaled(48, 48);
lb->setPixmap(pmPlayer);
if (19 != ((cp - &strmaze[0][0]) % 20) && '*' != cp[1/*检测右边有没有墙*/]){
if (this->bmove){
lb->move(0, -276);
this->bmove = false;
}
this->cp++;
pa->setEasingCurve(QEasingCurve::Linear);
pa->setStartValue(QRect(lb->x(), lb->y(), this->width(), this->height()));
pa->setEndValue(QRect(lb->x() + 48, lb->y(), this->width(), this->height()));
pa->start();
}
})
同理,其它方向的移动操作也可以写起来了。(让玩家指针cp
下移,只需要让它右移二维字符数组strmaze
一行的元素个数就行了。上移操作同理,检测上面或下面墙的操作也同理)
new QShortcut(QKeySequence("W"), this, [=](){
QPixmap pmPlayer(":/Playerw.png");//玩家移动时的朝向
pmPlayer = pmPlayer.scaled(48, 48);
lb->setPixmap(pmPlayer);
if (((cp - &strmaze[0][0]) / 20) && '*' != cp[-20/*检测上边有没有墙*/]){
if (this->bmove){
lb->move(0, -276);
this->bmove = false;
}
this->cp -= 20;
pa->setEasingCurve(QEasingCurve::Linear);
pa->setStartValue(QRect(lb->x(), lb->y(), this->width(), this->height()));
pa->setEndValue(QRect(lb->x(), lb->y() - 48, this->width(), this->height()));
pa->start();
}
}),
new QShortcut(QKeySequence("A"), this, [=](){
QPixmap pmPlayer(":/Players.png");//玩家移动时的朝向
pmPlayer = pmPlayer.scaled(48, 48);
lb->setPixmap(pmPlayer);
if (((cp - &strmaze[0][0]) % 20) && '*' != cp[-1/*检测左边有没有墙*/]){
if (this->bmove){
lb->move(0, -276);
this->bmove = false;
}
this->cp--;
pa->setEasingCurve(QEasingCurve::Linear);
pa->setStartValue(QRect(lb->x(), lb->y(), this->width(), this->height()));
pa->setEndValue(QRect(lb->x() - 48, lb->y(), this->width(), this->height()));
pa->start();
}
}),
new QShortcut(QKeySequence("S"), this, [=](){
QPixmap pmPlayer(":/Players.png");//玩家移动时的朝向
pmPlayer = pmPlayer.scaled(48, 48);
lb->setPixmap(pmPlayer);
if (19 != ((cp - &strmaze[0][0]) / 20) && '*' != cp[20/*检测下边有没有墙*/]){
if (this->bmove){
lb->move(0, -276);
this->bmove = false;
}
this->cp += 20;
pa->setEasingCurve(QEasingCurve::Linear);
pa->setStartValue(QRect(lb->x(), lb->y(), this->width(), this->height()));
pa->setEndValue(QRect(lb->x(), lb->y() + 48, this->width(), this->height()));
pa->start();
}
}),
之后,我们就测试一下,看到玩家标签lb
果然能正常的移动起来了。
接下来,我们就来完成最后的判断玩家胜利的逻辑了。这个逻辑呢,想起来简单,做起来也不难,只需要在每次移动后检测玩家指针cp
有没有到达终点G
的位置,就行了。
//在mainwindow.cpp里的startgame方法里,一般如果mainwindow.h没有这个Iswin信号,就请在mainwindow.h中定义这个信号
connect(this, &MainWindow::Iswin, [=](){
if ('G' == *this->cp){
ui->stackedWidget->setCurrentIndex(2);
}
});
而这,就是现在这个游戏的主要逻辑。
connect(this, &MainWindow::Iswin, [=](){
if ('G' == *this->cp){
ui->stackedWidget->setCurrentIndex(2);
}
});
new QShortcut(QKeySequence("W"), this, [=](){
QPixmap pmPlayer(":/Playerw.png");
pmPlayer = pmPlayer.scaled(48, 48);
lb->setPixmap(pmPlayer);
if (((cp - &strmaze[0][0]) / 20) && '*' != cp[-20]){
if (this->bmove){
lb->move(0, -276);
this->bmove = false;
}
this->cp -= 20;
pa->setEasingCurve(QEasingCurve::Linear);
pa->setStartValue(QRect(lb->x(), lb->y(), this->width(), this->height()));
pa->setEndValue(QRect(lb->x(), lb->y() - 48, this->width(), this->height()));
pa->start();
}
emit Iswin();
}),
new QShortcut(QKeySequence("A"), this, [=](){
QPixmap pmPlayer(":/Playera.png");
pmPlayer = pmPlayer.scaled(48, 48);
lb->setPixmap(pmPlayer);
if (((cp - &strmaze[0][0]) % 20) && '*' != cp[-1]){
if (this->bmove){
lb->move(0, -276);
this->bmove = false;
}
this->cp--;
pa->setEasingCurve(QEasingCurve::Linear);
pa->setStartValue(QRect(lb->x(), lb->y(), this->width(), this->height()));
pa->setEndValue(QRect(lb->x() - 48, lb->y(), this->width(), this->height()));
pa->start();
}
emit Iswin();
}),
new QShortcut(QKeySequence("S"), this, [=](){
QPixmap pmPlayer(":/Players.png");
pmPlayer = pmPlayer.scaled(48, 48);
lb->setPixmap(pmPlayer);
if (19 != ((cp - &strmaze[0][0]) / 20) && '*' != cp[20]){
if (this->bmove){
lb->move(0, -276);
this->bmove = false;
}
this->cp += 20;
pa->setEasingCurve(QEasingCurve::Linear);
pa->setStartValue(QRect(lb->x(), lb->y(), this->width(), this->height()));
pa->setEndValue(QRect(lb->x(), lb->y() + 48, this->width(), this->height()));
pa->start();
}
emit Iswin();
}),
new QShortcut(QKeySequence("D"), this, [=](){
QPixmap pmPlayer(":/Playerd.png");
pmPlayer = pmPlayer.scaled(48, 48);
lb->setPixmap(pmPlayer);
if (19 != ((cp - &strmaze[0][0]) % 20) && '*' != cp[1]){
if (this->bmove){
lb->move(0, -276);
this->bmove = false;
}
this->cp++;
pa->setEasingCurve(QEasingCurve::Linear);
pa->setStartValue(QRect(lb->x(), lb->y(), this->width(), this->height()));
pa->setEndValue(QRect(lb->x() + 48, lb->y(), this->width(), this->height()));
pa->start();
}
emit Iswin();
})
在你debug之后,如果你试着将玩家走到终点(虽然游戏窗口全屏时玩家还是在往下偏移),那么你就转到stackedwidget
的第3页了,可以看到一大片的空白,是因为我们还没有实现这个胜利界面,现在,就要实现这个游戏的最后一个界面——胜利界面了。
胜利界面
胜利界面的话,就只要达成以下三点要求就好了。
- 设
stackedwidget
第3页的背景颜色为白色 - 设表示胜利的标签的图片为资源中表示胜利的图片(要大点,太小就不好看)
- 在刚开始转到胜利界面之后,要播放表示胜利的音乐,播放完后就关闭游戏的窗口
首先,我们完成第一个要求。要完成第一个要求,先需要在mainwindow.h
中导入QPainter
类文件,并声明出paintEvent
方法,然后,在mainwindow.cpp
里定义paintEvent
方法之后,只需要在这个paintEvent
方法里new
一个QPainter
画家对象painter
,并让这个painter
画家对象在这一页中填充一下白色用来当这一页的背景的颜色,最后只需要让这些操作只执行一次就好了,方法随意(这里定义成员布尔型变量bwin
可以为之后的bug修复做准备)。
//在mainwindow.h里
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QLabel>
#include <QPainter>
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void startgame();
void paintEvent(QPaintEvent*);
signals:
void Iswin();
private:
Ui::MainWindow *ui;
bool bmove;
bool bwin;
char* cp;
};
#endif // MAINWINDOW_H
//在mainwindow.cpp里
void MainWindow::paintEvent(QPaintEvent*){
if (2 == ui->stackedWidget->indexOf(ui->stackedWidget->currentWidget())){
QPainter* painter = new QPainter(this);
painter->fillRect(this->rect(), Qt::white);
painter->end();
}
}
然后,我们要让表示胜利的标签的图片为资源中表示胜利的图片,很简单,只要在Mainwindow
的构造方法中setPixmap
一下就行了,如果图片小,那张图片就可以通过QPixmap
图片类中的关于scaled
的一些方法来放大。
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QPixmap pm(":/start.png");
QPixmap pma(":/title.png");
ui->label->setPixmap(pma);
ui->toolButton->setStyleSheet("QToolButton{border:0px;}");
ui->toolButton->setIconSize(QSize(200,200));
ui->toolButton->setIcon(pm);
connect(ui->toolButton, &QToolButton::clicked, [&](){
this->startgame();
});
connect(ui->actionkaishi1, &QAction::triggered, [&](){
this->startgame();
});
ui->actionkaishi1->setText("开始");
ui->label_2->setPixmap(QPixmap(":/win(1).png"));//这里的win(1).png为在该游戏程序外部由win.png放大之后的图片
this->bwin = false;
}
最后,要完成第3个要求,我们就要找到转到胜利界面的语句,找到之后,就导入一下类文件QSoundEffect
,我们就在这条语句的后面new
一个QSoundEffect
对象来,那这个对象要怎样new
呢?光看构造方式和QSoundEffect
的意思也不知道啊,没事,不知道的话,就用最简单的无参构造方式试着new
一下就好了,然后,就用刚new
出来的QSoundEffect
对象借助QUrl
类去设一下播放的音乐,最后,给它去定义一个链接,如果它播放好了,就把窗口关掉,游戏就结束了。
connect(this, &MainWindow::Iswin, [=](){
if ('G' == *this->cp){
ui->stackedWidget->setCurrentIndex(2);
QSoundEffect* sound = new QSoundEffect;
sound->setSource(QUrl::fromLocalFile("//资源中的win.wav胜利音效的路径,自己去改!"));//<<<<<<请见左
sound->play();
connect(sound, &QSoundEffect::playingChanged, [=](){
this->close();
});
}
});
至此,这个迷宫游戏基本也就完成了。接下来还有其它的bug要修,我们要尽力一下了。更新的地方代码和流程图这时都可以去看一下。
//触发Iswin信号之后的槽函数里
connect(this, &MainWindow::Iswin, [=](){
if ('G' == *this->cp){
ui->stackedWidget->setCurrentIndex(2);
QSoundEffect* sound = new QSoundEffect;
sound->setSource(QUrl::fromLocalFile("//资源中的win.wav胜利音效的路径,自己去改!"));//<<<<<<请见左
sound->play();
connect(sound, &QSoundEffect::playingChanged, [=](){
this->close();
});
}
});
//Mainwindow的构造函数里
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QPixmap pm(":/start.png");
QPixmap pma(":/title.png");
ui->label->setPixmap(pma);
ui->toolButton->setStyleSheet("QToolButton{border:0px;}");
ui->toolButton->setIconSize(QSize(200,200));
ui->toolButton->setIcon(pm);
connect(ui->toolButton, &QToolButton::clicked, [&](){
this->startgame();
});
connect(ui->actionkaishi1, &QAction::triggered, [&](){
this->startgame();
});
ui->actionkaishi1->setText("开始");
ui->label_2->setPixmap(QPixmap(":/win(1).png"));
this->bwin = false;
}
//Mainwindow的paintEvent方法里
void MainWindow::paintEvent(QPaintEvent*){
if (2 == ui->stackedWidget->indexOf(ui->stackedWidget->currentWidget())){
QPainter* painter = new QPainter(this);
painter->fillRect(this->rect(), Qt::white);
painter->end();
}
}
其它bug修复
首先,在“胜利界面”的标题旁,或者如果你刚才测试了一下游戏,那么你就会知道在游戏窗口变为全屏之后,这个玩家标签lb
就又往下偏移了很多格,修复它的话,只需要把玩家标签lb
往下偏移的像素数改一下就行了,因此还要再调整一下偏移的像素数。
//在movesc里的各个这样的语句里
if (this->bmove){
lb->move(0, -484);
this->bmove = false;
}
并且,游戏胜利时,重复的播放了胜利的音效,这不能,所以,就让Mainwindow
的成员布尔型变量bwin
在游戏胜利之后设为真就行了,并让触发Iswin
信号之后的胜利的操作设成一次性的就行。
connect(this, &MainWindow::Iswin, [=](){
if (!this->bwin && 'G' == *this->cp){
ui->stackedWidget->setCurrentIndex(2);
this->bwin = true;
QSoundEffect* sound = new QSoundEffect;
sound->setSource(QUrl::fromLocalFile("//资源中的win.wav胜利音效的路径,自己去改!"));//<<<
sound->play();
connect(sound, &QSoundEffect::playingChanged, [=](){
this->close();
});
}
});
然后,由于游戏开始时窗口根本无法完整显示迷宫,并且在每次调整窗口的大小后,玩家标签lb
移动后的位置飘忽不定,所以就要固定一下窗口的大小,防止出现这一些问题,那大小要怎么定呢,只需要记住长至少为迷宫的长+迷宫左边离布局的像素数+迷宫右边离布局右边的像素数,宽至少为迷宫的宽+迷宫左边离布局的像素数+迷宫右边离布局右边的像素数,就行了。(窗口的长和宽越短越好,但如果有bug可能就除外,具体窗口的长与宽请自己调出来)
this->setFixedSize(1017, 1017);//示例
接着,我们需要为游戏窗口设置一下icon和标题,直接照着粘贴就行。
this->setWindowIcon(QPixmap(":/icon.png"));
this->setWindowTitle("迷宫");
再接着,如果你在测试这个游戏的时候把玩家移动终点了,那游戏就会直接胜利,不会执行玩家移动到终点的动画了,因此,就要导入一下QTimer
类文件,用singleShot
在玩家胜利之后等待一下吧。
connect(this, &MainWindow::Iswin, [=](){
if (!this->bwin && 'G' == *this->cp){
this->bwin = true;
QTimer::singleShot(300, [=](){
ui->stackedWidget->setCurrentIndex(2);
QSoundEffect* sound = new QSoundEffect;
sound->setSource(QUrl::fromLocalFile("//资源中的win.wav胜利音效的路径,自己去改!"));
sound->play();
connect(sound, &QSoundEffect::playingChanged, [=](){
this->close();
});
});
}
});
最后,就要给移动操作设一下冷却的时机了。要给移动操作设置冷却的时机,你就要知道,一般在玩家移动时的那段时间里就是最好的要冷却的时机,所以如果玩家开始移动了,那么就给所有的玩家移动的操作都不执行了;而如果玩家结束移动了,那么就给所有的玩家移动的操作都执行了,就是那么简单。
new QShortcut(QKeySequence("W"), this, [=](){
if (!this->bwait) {
this->bwait = true;
QPixmap pmPlayer(":/Playerw.png");
pmPlayer = pmPlayer.scaled(48, 48);
lb->setPixmap(pmPlayer);
if (this->bwait = ((cp - &strmaze[0][0]) / 20) && '*' != cp[-20]){
if (this->bmove){
lb->move(0, -484);
this->bmove = false;
}
this->cp -= 20;
pa->setEasingCurve(QEasingCurve::Linear);
pa->setStartValue(QRect(lb->x(), lb->y(), this->width(), this->height()));
pa->setEndValue(QRect(lb->x(), lb->y() - 48, this->width(), this->height()));
pa->start();
}
emit Iswin();
}
}),
new QShortcut(QKeySequence("A"), this, [=](){
if (!this->bwait) {
this->bwait = true;
QPixmap pmPlayer(":/Playera.png");
pmPlayer = pmPlayer.scaled(48, 48);
lb->setPixmap(pmPlayer);
if (this->bwait = ((cp - &strmaze[0][0]) % 20) && '*' != cp[-1]){
if (this->bmove){
lb->move(0, -484);
this->bmove = false;
}
this->cp--;
pa->setEasingCurve(QEasingCurve::Linear);
pa->setStartValue(QRect(lb->x(), lb->y(), this->width(), this->height()));
pa->setEndValue(QRect(lb->x() - 48, lb->y(), this->width(), this->height()));
pa->start();
}
emit Iswin();
}
}),
new QShortcut(QKeySequence("S"), this, [=](){
if (!this->bwait) {
this->bwait = true;
QPixmap pmPlayer(":/Players.png");
pmPlayer = pmPlayer.scaled(48, 48);
lb->setPixmap(pmPlayer);
if (this->bwait = 19 != ((cp - &strmaze[0][0]) / 20) && '*' != cp[20]){
if (this->bmove){
lb->move(0, -484);
this->bmove = false;
}
this->cp += 20;
pa->setEasingCurve(QEasingCurve::Linear);
pa->setStartValue(QRect(lb->x(), lb->y(), this->width(), this->height()));
pa->setEndValue(QRect(lb->x(), lb->y() + 48, this->width(), this->height()));
pa->start();
}
emit Iswin();
}
}),
new QShortcut(QKeySequence("D"), this, [=](){
if (!this->bwait) {
this->bwait = true;
QPixmap pmPlayer(":/Playerd.png");
pmPlayer = pmPlayer.scaled(48, 48);
lb->setPixmap(pmPlayer);
if (this->bwait = 19 != ((cp - &strmaze[0][0]) % 20) && '*' != cp[1]){
if (this->bmove){
lb->move(0, -484);
this->bmove = false;
}
this->cp++;
pa->setEasingCurve(QEasingCurve::Linear);
pa->setStartValue(QRect(lb->x(), lb->y(), this->width(), this->height()));
pa->setEndValue(QRect(lb->x() + 48, lb->y(), this->width(), this->height()));
pa->start();
}
emit Iswin();
}
})
好了,我们的Qt简单迷宫游戏也就正式完成了,让我们一起看一下我们做的Qt简单迷宫游戏,回顾bug修复的部分,在博客的最后再见吧。
Qt简单迷宫游戏
connect(this, &MainWindow::Iswin, [=](){
if (!this->bwin && 'G' == *this->cp){
this->bwin = true;
QTimer::singleShot(300, [=](){//直接转到胜利界面bug
ui->stackedWidget->setCurrentIndex(2);
QSoundEffect* sound = new QSoundEffect;
sound->setSource(QUrl::fromLocalFile("//资源中的win.wav胜利音效的路径,自己去改!"));
sound->play();
connect(sound, &QSoundEffect::playingChanged, [=](){
this->close();
});
}
}
new QShortcut(QKeySequence("W"), this, [=](){
if (!this->bwait) {//无冷却bug
this->bwait = true;//无冷却bug
QPixmap pmPlayer(":/Playerw.png");
pmPlayer = pmPlayer.scaled(48, 48);
lb->setPixmap(pmPlayer);
if (this->bwait = ((cp - &strmaze[0][0]) / 20) && '*' != cp[-20]){//无冷却bug
if (this->bmove){
lb->move(0, -484);
this->bmove = false;
}
this->cp -= 20;
pa->setEasingCurve(QEasingCurve::Linear);0
pa->setStartValue(QRect(lb->x(), lb->y(), this->width(), this->height()));
pa->setEndValue(QRect(lb->x(), lb->y() - 48, this->width(), this->height()));
pa->start();
}
emit Iswin();
}
}),
new QShortcut(QKeySequence("A"), this, [=](){
if (!this->bwait) {//无冷却bug
this->bwait = true;//无冷却bug
QPixmap pmPlayer(":/Playera.png");
pmPlayer = pmPlayer.scaled(48, 48);
lb->setPixmap(pmPlayer);
if (this->bwait = ((cp - &strmaze[0][0]) % 20) && '*' != cp[-1]){//无冷却bug
if (this->bmove){
lb->move(0, -484);
this->bmove = false;
}
this->cp--;
pa->setEasingCurve(QEasingCurve::Linear);
pa->setStartValue(QRect(lb->x(), lb->y(), this->width(), this->height()));
pa->setEndValue(QRect(lb->x() - 48, lb->y(), this->width(), this->height()));
pa->start();
}
emit Iswin();
}
}),
new QShortcut(QKeySequence("S"), this, [=](){
if (!this->bwait) {//无冷却bug
this->bwait = true;//无冷却bug
QPixmap pmPlayer(":/Players.png");
pmPlayer = pmPlayer.scaled(48, 48);
lb->setPixmap(pmPlayer);
if (this->bwait = 19 != ((cp - &strmaze[0][0]) / 20) && '*' != cp[20]){//无冷却bug
if (this->bmove){
lb->move(0, -484);
this->bmove = false;
}
this->cp += 20;
pa->setEasingCurve(QEasingCurve::Linear);
pa->setStartValue(QRect(lb->x(), lb->y(), this->width(), this->height()));
pa->setEndValue(QRect(lb->x(), lb->y() + 48, this->width(), this->height()));
pa->start();
}
emit Iswin();
}
}),
new QShortcut(QKeySequence("D"), this, [=](){
if (!this->bwait) {//无冷却bug
this->bwait = true;//无冷却bug
QPixmap pmPlayer(":/Playerd.png");
pmPlayer = pmPlayer.scaled(48, 48);
lb->setPixmap(pmPlayer);
if (this->bwait = 19 != ((cp - &strmaze[0][0]) % 20) && '*' != cp[1]){//无冷却bug
if (this->bmove){
lb->move(0, -484);
this->bmove = false;
}
this->cp++;
pa->setEasingCurve(QEasingCurve::Linear);
pa->setStartValue(QRect(lb->x(), lb->y(), this->width(), this->height()));
pa->setEndValue(QRect(lb->x() + 48, lb->y(), this->width(), this->height()));
pa->start();
}
emit Iswin();
}
})
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QPixmap pm(":/start.png");
QPixmap pma(":/title.png");
ui->label->setPixmap(pma);
ui->toolButton->setStyleSheet("QToolButton{border:0px;}");
ui->toolButton->setIconSize(QSize(200,200));
ui->toolButton->setIcon(pm);
connect(ui->toolButton, &QToolButton::clicked, [&](){
this->startgame();
});
connect(ui->actionkaishi1, &QAction::triggered, [&](){
this->startgame();
});
ui->actionkaishi1->setText("开始");
ui->label_2->setPixmap(QPixmap(":/win(1).png"));
this->bwin = false;
this->setFixedSize(1017, 1017);//无法完整显示迷宫,并且在每次调整窗口的大小后,玩家标签lb移动后的位置飘忽不定bug
this->setWindowIcon(QPixmap(":/icon.png"));//无iconBug
this->setWindowTitle("迷宫");//标题默认bug
}
你可扩展
如果你觉得这个游戏玩起来之后感到不好玩,那么就可以给你的游戏扩展这一些东西。
- 随机生成迷宫
- 限时闯迷宫
- 多人迷宫竞赛
- ……
下一篇博客要说的东西
链表的介绍