当前位置: 首页 > article >正文

Qt简单迷宫游戏

目录

  • 你将学到
  • 你将准备
  • 你将改变
  • 你将设计
  • 你将编程
    • 开始界面
    • 游玩界面
    • 胜利界面
    • 其它bug修复
  • 你可扩展
  • 下一篇博客要说的东西

你将学到

  • QtQKeySequence对象的基本创建
  • QtQShortcut对象的基本应用
  • QtQSoundEffect对象的基本应用

你将准备

在开始制作Qt简单迷宫游戏之前,我们先要准备这一些东西。


  1. 这样,一个关于Qt简单迷宫游戏的项目就正式构建完成了。

你将改变

之后,我们我将要改变一些东西在这个游戏的项目里。


  1. 在你做改好这一些东西之后,你的Qt简单迷宫游戏就可以发出音效并使用图片了。

你将设计

为了完成这个迷宫游戏,我们也要设计一下UI,才能让用户看着舒服。


  1. 之后,你的迷宫游戏所有界面的UI的基本框架也就构建完成了。

你将编程

开始界面

在编程之前,如果没有一个做这个迷宫游戏的方法或策略的话,那么对于一个普通人来说,小型的项目大多数可能还是很快就可以能想到怎么做的,而大型的项目呢,则根本做不了,简直无从下手就像打怪一样,弱的怪,你轻轻松松就能打败;而强的怪呢,即使你有实力,但你如果没有策略的话,就很难打败他了,甚至你会因此而失败!因此,我们在做这个迷宫游戏,乃至其他的东西的时候,最好要制定一个方法或策略来做这个东西。看到这,你或许就可以想一下这游戏到底要怎么做了,当然,你也可以看下下面的建议方法。

转到开始界面,点击开始按钮开始游戏
开始玩迷宫游戏,玩家走到终点后就自动跳转到胜利界面
跳转到胜利界面之后播放胜利的声音,播好之后就关闭窗口

在上面,你可以知道,首先要实现开始界面,就要为开始界面里的一些UI实现一些用于开始的功能,那么要怎样开始呢?很简单,只需要把这个迷宫游戏的UI里的stackwidget的当前索引设为1就行了,当然,为了使游戏的开始界面更美观,我们可以把资源里的图片设置于当标题图片用的QLabel标签label和用于当开始按钮的QToolButtonIcon,并使菜单栏中刚才唯一设置过的名为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("开始");
}
开始,设置ui
定义QPixmap图片pm为资源里的图片start.png
定义QPixmap图片pma为资源里的图片title.png
设用来当标题图片用的Label标签label的图片为QPixmap图片pma
设用来当开始按钮的ToolButton的边框宽度为0像素
设用来当开始按钮的ToolButton的Icon的大小为200x200
设用来当开始按钮的ToolButton的Icon为QPixmap图片pm
设用来当开始按钮的ToolButton的点击后信号所触发的槽函数为“让stackedwidget转到第2页(会改,原因请见下面)”
设用来当开始按钮的ToolButton的点击后信号所触发的槽函数为“让stackedwidget转到第2页(会改,原因请见下面)”
设菜单栏中的开始动作的名设为“开始”
结束开始界面的实现

在开始界面实现好之后,我们可以来测试一下这个开始界面有没有bug。测试好之后,如果你发现了stackwidget当前跳转的页不对,那么请转到刚才的设计界面,右键选中stackWidget,并点击改变页顺序,在这里,你可以修正这个stackwidget当前跳转的页不对”的bug,具体实现步骤如下。


  1. 修正好这个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();
}
break
break
break
开始
让stackedwidget转到第2页
定义有20行20列的二维字符数组strmaze为上面代码中关于迷宫的这一个二维字符数组
定义QLabel标签对象lb为new出来的在stackedwidget第2页的QLabel标签
定义QPixmap图片pm,并设它的大小为48x48
定义整型i为0
i <= 400?
设标签lb为new出来的在stackedwidget第2页的QLabel标签
400 == i?
把标签lb移到x坐标为0,y坐标为0的位置上
让图片pm加载资源里的玩家向右的图片
设图片pm为大小为48x48的图片pm
设Qlabel标签lb的图片为图片pm
显示标签lb
i自增1
结束
把标签lb移到x坐标为整型i模上20再乘48的结果,y坐标为整型i除以20再乘48的结果的位置上
'*' == strmaze[i / 20][i % 20]?
让图片pm加载资源里的墙的图片
'G' == strmaze[i / 20][i % 20]?
让图片pm加载资源里的终点的图片
让图片pm填充白色

在做好显示迷宫的代码后,我们就要对这个玩家标签lb进行移动操作了。移动,肯定是要有个操作方式来执行的,比如大家最广为人知的键盘,又比如PushButtonToolButton……而这次,我们要用键盘来实现移动的功能。首先,我们要想一种既得心应手又畅通无阻而且可以接收键盘按键按下的信号的东西,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();
})
右移操作(按下“D”键的操作)
下移操作(按下“S”键的操作)
左移操作(按下“A”键的操作)
上移操作(按下“W”键的操作)
判断胜利(触发Iswin信号所执行的槽函数)
触发Iswin信号,并结束
开始动画pa
设动画pa的终点为窗口中x坐标为标签lb的x坐标加48的结果,y坐标为标签lb的y坐标的地方
设动画pa的起点为窗口中x坐标为标签lb的x坐标,y坐标为标签lb的y坐标的地方
设QPropertyAnimation动画pa的动画曲线为直线
把Mainwindow的成员字符指针cp向右移动1位
设Mainwindow的成员布尔值bmove为假
把标签lb移到x坐标为0,y坐标为-276的地方(y坐标要改,原因请见下面)
this->bmove?
19 != ((cp - &strmaze[0][0]) % 20) && '*' != cp[1]?
设标签lb的图片为图片pmPlayer
设图片pmPlayer为大小为48x48的图片pmPlayer
定义QPixmap图片pmPlayer,并让图片pmPlayer加载资源里玩家向上的图片
开始
触发Iswin信号,并结束
开始动画pa
设动画pa的终点为窗口中x坐标为标签lb的x坐标,y坐标为标签lb的y坐标加48的结果的地方
设动画pa的起点为窗口中x坐标为标签lb的x坐标,y坐标为标签lb的y坐标的地方
设QPropertyAnimation动画pa的动画曲线为直线
把Mainwindow的成员字符指针cp向右移动20位
设Mainwindow的成员布尔值bmove为假
把标签lb移到x坐标为0,y坐标为-276的地方(y坐标要改,原因请见下面)
this->bmove?
19 != ((cp - &strmaze[0][0]) / 20) && '*' != cp[20]?
设标签lb的图片为图片pmPlayer
设图片pmPlayer为大小为48x48的图片pmPlayer
定义QPixmap图片pmPlayer,并让图片pmPlayer加载资源里玩家向下的图片
开始
触发Iswin信号,并结束
开始动画pa
设动画pa的终点为窗口中x坐标为标签lb的x坐标减48的结果,y坐标为标签lb的y坐标的地方
设动画pa的起点为窗口中x坐标为标签lb的x坐标,y坐标为标签lb的y坐标的地方
设QPropertyAnimation动画pa的动画曲线为直线
把Mainwindow的成员字符指针cp向左移动1位
设Mainwindow的成员布尔值bmove为假
把标签lb移到x坐标为0,y坐标为-276的地方(y坐标要改,原因请见下面)
this->bmove?
((cp - &strmaze[0][0]) % 20) && '*' != cp[-1]?
设标签lb的图片为图片pmPlayer
设图片pmPlayer为大小为48x48的图片pmPlayer
定义QPixmap图片pmPlayer,并让图片pmPlayer加载资源里玩家向左的图片
开始
触发Iswin信号,并结束
开始动画pa
设动画pa的终点为窗口中x坐标为标签lb的x坐标的结果,y坐标为标签lb的y坐标减48的结果的地方
设动画pa的起点为窗口中x坐标为标签lb的x坐标,y坐标为标签lb的y坐标的地方
设QPropertyAnimation动画pa的动画曲线为直线
把Mainwindow的成员字符指针cp向左移动20位
设Mainwindow的成员布尔值bmove为假
把标签lb移到x坐标为0,y坐标为-276的地方(y坐标要改,原因请见下面)
this->bmove?
((cp - &strmaze[0][0]) / 20) && '*' != cp[-20]?
设标签lb的图片为图片pmPlayer
设图片pmPlayer为大小为48x48的图片pmPlayer
定义QPixmap图片pmPlayer,并让图片pmPlayer加载资源里玩家向上的图片
开始
结束
让stackedwidget转到第3页
'G' == *this->cp?
开始

在你debug之后,如果你试着将玩家走到终点(虽然游戏窗口全屏时玩家还是在往下偏移),那么你就转到stackedwidget的第3页了,可以看到一大片的空白,是因为我们还没有实现这个胜利界面,现在,就要实现这个游戏的最后一个界面——胜利界面了。

胜利界面

胜利界面的话,就只要达成以下三点要求就好了。

  1. stackedwidget第3页的背景颜色为白色
  2. 设表示胜利的标签的图片为资源中表示胜利的图片(要大点,太小就不好看)
  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();
    }
}

Mainwindow的paintEvent方法里
结束
开始
2 == ui->stackedWidget->indexOf(ui->stackedWidget->currentWidget())?
定义一个QPainter对象painter为new出来的QPainter对象
让画家对象painter把整个窗口填充成白色
让画家对象painter停止绘画
Mainwindow的构造函数里
结束
开始
...(开始界面的实现)
设用来表示胜利的标签label_2的图片为资源里名为win(1).png的表示胜利的图片
设Mainwindow的成员布尔型变量bwin为假
触发Iswin信号之后的槽函数里
结束
开始
'G' == *this->cp?
让stackedwidget转到第2页
定义QSoundEffect对象sound为new出来的QSoundEffect对象
设音乐sound为资源中名为win.wav的胜利音效
播放音乐sound
将音乐sound的结束信号所触发的槽函数设为“关掉窗口”

其它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
}
Mainwindow的构造函数里
结束
开始
...(开始界面的实现及成员变量的初始化)
设游戏窗口的固定大小为1017x1017
设游戏窗口的icon为资源里名为icon.png的图片
设游戏窗口的标题为“迷宫”
玩家右移的操作里
结束
开始
!this->bwait?
设Mainwindow的成员布尔型变量bwait为真
...(给玩家标签lb的图片设置成资源里玩家向右的图片)
this->bwait = 19 != ((cp - &strmaze[0][0]) % 20) && '*' != cp[1]
...(右移操作)
触发Iswin信号
玩家下移的操作里
结束
开始
!this->bwait?
设Mainwindow的成员布尔型变量bwait为真
...(给玩家标签lb的图片设置成资源里玩家向下的图片)
this->bwait = 19 != ((cp - &strmaze[0][0]) / 20) && '*' != cp[20]
...(下移操作)
触发Iswin信号
玩家左移的操作里
结束
开始
!this->bwait?
设Mainwindow的成员布尔型变量bwait为真
...(给玩家标签lb的图片设置成资源里玩家向左的图片)
this->bwait = ((cp - &strmaze[0][0]) % 20) && '*' != cp[-1]
...(左移操作)
触发Iswin信号
玩家上移的操作里
结束
开始
!this->bwait?
设Mainwindow的成员布尔型变量bwait为真
...(给玩家标签lb的图片设置成资源里玩家向上的图片)
this->bwait = ((cp - &strmaze[0][0]) / 20) && '*' != cp[-20]
...(上移操作)
触发Iswin信号
触发Iswin信号之后的槽函数里
等待0.3秒
结束
开始
!this->bwin && 'G' == *this->cp
设Mainwindow里的成员布尔型变量bwin为真
...(胜利的操作)

你可扩展

如果你觉得这个游戏玩起来之后感到不好玩,那么就可以给你的游戏扩展这一些东西。

  • 随机生成迷宫
  • 限时闯迷宫
  • 多人迷宫竞赛
  • ……

下一篇博客要说的东西

链表的介绍


http://www.kler.cn/a/518983.html

相关文章:

  • erase() 【删数函数】的使用
  • 【fly-iot飞凡物联】(20):2025年总体规划,把物联网整套技术方案和实现并落地,完成项目开发和课程录制。
  • 青少年编程与数学 02-007 PostgreSQL数据库应用 15课题、备份与还原
  • hedfs和hive数据迁移后校验脚本
  • tkinter绘制组件(44)——浮出ui控件
  • IOS 自定义代理协议Delegate
  • 解数独力扣
  • MATLAB 工具库的使用说明和案例示例
  • 双写+灰度发布:高并发场景下的维度表拆分零事故迁移实践
  • Mono里运行C#脚本36—加载C#类定义的成员变量和方法的数量
  • 【数据结构】树的基本:结点、度、高度与计算
  • vue路由history模式springBoot/Nginx配置
  • 【优选算法】11----最大连续1的个数|||
  • 【湖北省乡镇界】面图层arcgis数据乡镇名称和编码wgs84坐标无偏移shp格式内容测评
  • debian12.9编译freeswitch1.10.12【默认安装】
  • 掌握Gradle构建脚本:Kotlin DSL配置指南与最佳实践
  • 机器学习day3
  • 本地Ubuntu轻松部署高效性能监控平台SigNoz与远程使用教程
  • 71.在 Vue 3 中使用 OpenLayers 实现按住 Shift 拖拽、旋转和缩放效果
  • Linux MySQL离线安装
  • 《深入解析:DOS检测的技术原理与方法》
  • 9.business english-agreement
  • WPS添加文本超简单,批量操作不费劲-Excel易用宝
  • 基于Flask框架和Hive数仓的农业数据分析系统
  • 【论文阅读】RT-SKETCH: GOAL-CONDITIONED IMITATION LEARNING FROM HAND-DRAWN SKETCHES
  • 设计模式的艺术-中介者模式