用Qt写一个迷宫游戏

用Qt写一个迷宫游戏

一段时间以前,我用c写了个使用随机数生成迷宫的代码,现在在自学学Qt,于是就想到了利用这个代码改一个小游戏。
现在记录一下自己的学习过程,也算抛砖引玉,各位看官请多指教。
话不多说现在开始。
使用的是Qt的IDE:QT Creater

step 1:把迷宫生成的c代码用c++类改写

为了方便,先把代码改写成类,诸位有兴趣可以拿去一玩。
代码丑了点,但是能用就行了。

maze.h

#ifndef MAZE_H
#define MAZE_H
/**************************************************
 * -1:边界 0:墙壁
 * 1:空单元(初始化后未判定的单元)
 * 2:待定墙壁(新建已定单元时建立)
 * 3:已定单元(确定迷宫树已延伸到的单元)
 * 4:打通墙壁(延伸操作)
 * 5.起点
 * 6.终点
 * 7.已经过路径
 ***************************************************/
class maze {
private:
    int level;  //迷宫阶数
    int** map;  //地图存储空间
    int start_x, start_y;   //起终点
    //生成基础地图(单元格)
    void base();
    //使点的周围设为待定(2)
    void _2(int i, int j);
    //设定迷宫开始延伸的起点
    void start() ;
    //循环停止判定(是否存在未判定的区域)
    bool judge();
    //操作(如果相邻空单元(1)则打通(变为4),如果不相邻空单元则为墙壁(0))
    void op(int i, int j);
    //随机选择一个待定墙壁判断并操作
    void random2();
public:
    //构造函数申请内存空间
    maze(int in_level);
    ~maze();
    //获取地图
    int getlevel() ;
    int** getmap();
    int getside();
    //生成地图
    void makemap();
    int p_x, p_y; //当前位置
    //重置地图
    void rebuildmap();
    int* operator[](int index);
};
#endif // MAZE_H

maze.cpp

#include "maze.h"
//生成基础地图(单元格)
void maze::base() {
    for (int i = 0; i < level * 2 + 1; i++) {
        for (int j = 0; j < level * 2 + 1; j++) {
            if (i == 0 || j == 0 || i == level * 2 + 1 - 1 ||
                j == level * 2 + 1 - 1) {
                map[i][j] = -1;
            } else if (i % 2 != 0 && j % 2 != 0) {
                map[i][j] = 1;
            } else {
                map[i][j] = 0;
            }
        }
    }
}
//使点的周围设为待定(2)
void maze:: _2(int i, int j) {
    if (map[i - 1][j] == 0) {
        map[i - 1][j] = 2;
    }
    if (map[i + 1][j] == 0) {
        map[i + 1][j] = 2;
    }
    if (map[i][j - 1] == 0) {
        map[i][j - 1] = 2;
    }
    if (map[i][j + 1] == 0) {
        map[i][j + 1] = 2;
    }
}
//设定迷宫开始延伸的起点
void maze:: start() {
    map[start_x][start_y] = 5;
    _2(start_x, start_y);
}
//循环停止判定(是否存在未判定的区域)
bool maze:: judge() {
    bool flag = 0;
    for (int i = 0; i < level * 2 + 1; i++) {
        for (int j = 0; j < level * 2 + 1; j++) {
            if (map[i][j] == 2) {
                flag = 1;
                return flag;
            }
        }
    }
    return flag;
}
//操作(如果相邻空单元(1)则打通(变为4),如果不相邻空单元则为墙壁(0))
void maze:: op(int i, int j) {
    if ((map[i - 1][j] == 3 || map[i - 1][j] == 5) && map[i + 1][j] == 1) {
        map[i][j] = 4;
        map[i + 1][j] = 3;
        _2(i + 1, j);
        start_x = i + 1;
        start_y = j;
    } else if ((map[i][j - 1] == 3 || map[i][j - 1] == 5) &&
               map[i][j + 1] == 1) {
        map[i][j] = 4;
        map[i][j + 1] = 3;
        _2(i, j + 1);
        start_x = i;
        start_y = j + 1;
    } else if ((map[i + 1][j] == 3 || map[i + 1][j] == 5) &&
               map[i - 1][j] == 1) {
        map[i][j] = 4;
        map[i - 1][j] = 3;
        _2(i - 1, j);
        start_x = i - 1;
        start_y = j;
    } else if ((map[i][j + 1] == 3 || map[i][j + 1] == 5) &&
               map[i][j - 1] == 1) {
        map[i][j] = 4;
        map[i][j - 1] = 3;
        _2(i, j - 1);
        start_x = i;
        start_y = j - 1;
    } else {
        map[i][j] = 0;
    }
}
//随机选择一个待定墙壁判断并操作
void maze:: random2() {
    int t = 0;
    for (int i = 0; i < level * 2 + 1; i++) {
        for (int j = 0; j < level * 2 + 1; j++) {
            if (map[i][j] == 2) {
                t++;
            }
        }
    }
    int k = rand() % t + 1;
    t = 0;
    for (int i = 0; i < level * 2 + 1; i++) {
        for (int j = 0; j < level * 2 + 1; j++) {
            if (map[i][j] == 2) {
                t++;
                if (t == k) {
                    op(i, j);
                    goto loopout;
                }
            }
        }
    }
loopout:
    if (!judge()) {
        map[start_x][start_y] = 6;
    }
}

//构造函数申请内存空间
maze:: maze(int in_level) : level(in_level) {
    map = new int* [level * 2 + 1];
    for (int i = 0; i < level * 2 + 1; i++) {
        map[i] = new int[level * 2 + 1];
    }
    start_x = 1, start_y = 1; //起点设置为(1,1)
}
maze::~maze() {
    for (int i = 0; i < level * 2 + 1; i++) {
        delete [] map[i];
    }
    delete [] map;
}
//获取地图
int maze:: getlevel() {
    return maze::level;
}
int** maze::getmap() {
    return map;
}
int maze::getside() {
    return level * 2 + 1;
}
//生成地图
void maze:: makemap() {
    p_x = start_x;
    p_y = start_y;
    base();
    start();
    int a = 0;
    while (judge()) {
        a++;
        random2();
        // if (a % 30 == 0) {
        //     printarr(map, level);
        //     system("PAUSE");
        // }
    }
}
int* maze::operator[](int index) {
    return map[index];
}
//重置地图
void maze::rebuildmap(){
    start_x=1;
    start_y=1;
    makemap();
}

好现在就可以生成一个迷宫了。
我把一开始的栅格大小叫做迷宫的阶数,现在可以通过构造函数生成特定特定阶数的迷宫,这阶数就可以变相的看作难度。

step 2:创建ui

首先我们创建一个Qt widgets application 项目,叫maze好了。
类名就叫mazeWidget,然后基类选择QWidget,剩下默认即可。
然后修改mazewidget.ui。

ui

先把主窗口字体改成14号。
拖入7个PushButton,5个Label,三个Spacer。
然后分别把对象名(objectName)、显示文本(text)改成:

QPushButton:
	start_btn 开始游戏
	stop_ptn 暂停
	end_btn 终止游戏
	rule_btn 规则
	setting_btn 设置
	battle_btn 双人对战
	about_btn 关于
QLabel:
	plaque 迷宫游戏
	plaque_time 时间:
	time_value 0
	plaque_grade 分数:
	grade_value 0

然后修改对象属性,其中暂停、终止游戏、双人对战按钮默认禁用(enabled = false)。
各个标签文字水平、垂直分别居中显示(修改alignment)。
然后选中这些按钮,应用垂直的布局器。
排列成这个样子:
右侧操作栏

现在再拖入一个Frame、一个ProgressBar,也应用垂直布局器。
ProgressBar隐藏文字(textVisible = false)。
Frame设置边框形状(frameShape)为Box,边框阴影(frameShadow)为Raised。
最后给整个窗口应用水平布局器,效果应该是这样的:
界面

这里先不用管界面风格

ok,那么到此ui就完成了,接下来我们来写逻辑。

step 3:窗口逻辑

第一步:在项目中加入我们的迷宫类文件。

加入好后如图所示:
项目文件列表

第二步:我们的窗口类头文件的编写

首先是头文件的加入:

#include <QMessageBox>      //Qt信息窗口头文件
#include <QPainter>         //Qt绘图头文件
#include <QDebug>           //QtDebug头文件
#include <QKeyEvent>        //Qt按键事件头文件
#include <QTimer>           //Qt计时器头文件
#include <QInputDialog>     //Qt输入对话框头文件
#include "maze.h"           //迷宫类头文件

其次是函数的声明:

protected:
    void paintEvent(QPaintEvent*);        //绘图事件
    void keyPressEvent(QKeyEvent*);       //按键按下事件
private slots:
    void on_start_btn_clicked();    //|
    void on_stop_ptn_clicked();     //|
    void on_end_btn_clicked();      //|
    void on_rule_btn_clicked();     //|各按钮点击槽函数
    void on_setting_btn_clicked();  //|
    void on_battle_btn_clicked();   //|
    void on_about_btn_clicked();    //|

    void time_update();             //时间更新槽函数

其中绘图时间与按键按下事件是继承自QWidget类的虚函数,实现了绘制迷宫地图和响应键盘按下的功能,绘图事件函数在窗口移动、改变大小、调用repaint()函数等情况是自动调用,键盘按下事件函数在键盘按下时调用,这两个函数名称不可以修改。
之后的7个函数是我们ui按钮点击时调用的槽函数,可以通过设计界面右击对应的按钮,点击转到槽来自动生成函数的声明和实现(定义)的框架,在这里是自动生成的函数名,不可以更改。
最后是我们自己定义的时间更新的槽函数,在计时器(QTimer)发出timeout()信号时调用,由于是自己定义的函数,名称可以修改,只要在代码中做对应修改就可以,需要手动对信号和槽进行链接。

最后是窗口类成员变量的声明:

private:
    Ui::mazeWidget* ui;         //ui对象指针
    maze* map;                  //迷宫对象指针

    bool painting_switch;       //绘图开关
    bool timing_switch;         //计时开关
    bool keybord_switch;        //键盘响应开关
    bool stop_switch;           //暂停按钮状态

    int grade;                  //分数
    int time;                   //时间

    QTimer* timer;				//计时器对象指针

其中ui对象指针不需要我们写,Qt自动生成。
迷宫对象与计时器对象指针用来实现迷宫与计时对应功能。
剩余变量用来存储游戏运行中状态与数据。

那么好,窗口类头文件的编写就完成了。
接下来是各函数的实现

第三步:窗口类源文件的编写

首先需要添加头文件"mazewidget.h"

#include "mazewidget.h"
#include "ui_mazewidget.h"

接下来开始挨个编写函数:

1.构造函数
#include "mazewidget.h"
#include "ui_mazewidget.h"
mazeWidget::mazeWidget(QWidget* parent)
	: QWidget(parent)
	, ui(new Ui::mazeWidget), map(new maze(20)), painting_switch(false), timing_switch(false)
	, keybord_switch(false), grade(0), time(0) {
	//TODO:状态栏
	ui->setupUi(this);
	ui->progressBar->setVisible(false);                 //初始隐藏进度条
	ui->end_btn->setEnabled(false);                     //设置终止按钮禁用
	ui->stop_ptn->setEnabled(false);                    //设置暂停按钮禁用
	ui->grade_value->setText("  ");                     //设置分数值显示为空
	ui->time_value->setText("  ");                      //设置时间值显示为空
	map->makemap();                                     //生成地图
	timer = new QTimer(this);                                           //初始化计时器
	connect(timer, &QTimer::timeout, this, &mazeWidget::time_update);   //链接时间更新信号与槽
}
2.析构函数

加入两行:

mazeWidget::~mazeWidget() {
	delete ui;
    delete map;		//1.
    delete timer;	//2.
}
3.绘图事件虚函数
void mazeWidget::paintEvent(QPaintEvent*) {
	if(!painting_switch) return;
	QPainter painter(this);     //画笔对象
	//绘图逻辑:
	int perblock = (std::min(ui->frame->width(), ui->frame->height()) - 20) / (map->getside());
	int start_x = ui->frame->x() + (ui->frame->width() - (ui->frame->x() + (map->getside()) * perblock)) / 2;
	int strat_y = ui->frame->y() + (ui->frame->height() - (ui->frame->y() + (map->getside()) * perblock)) / 2;
	for(int i = 0; i < map->getlevel() * 2 + 1; i++) {
		for(int j = 0; j < map->getlevel() * 2 + 1; j++) {
			if(i == map->p_x && j == map->p_y) {
				painter.fillRect(start_x + i * perblock, strat_y + j * perblock, perblock, perblock, QBrush(Qt::red));
			} else if(map->getmap()[i][j] == 7) {
				painter.fillRect(start_x + i * perblock, strat_y + j * perblock, perblock, perblock, QBrush(Qt::yellow));
			} else if(map->getmap()[i][j] == 3 || map->getmap()[i][j] == 4) {
				painter.fillRect(start_x + i * perblock, strat_y + j * perblock, perblock, perblock, QBrush(Qt::gray));
			} else if(map->getmap()[i][j] == 5) {
				painter.fillRect(start_x + i * perblock, strat_y + j * perblock, perblock, perblock, QBrush(Qt::blue));
			} else if(map->getmap()[i][j] == 6) {
				painter.fillRect(start_x + i * perblock, strat_y + j * perblock, perblock, perblock, QBrush(Qt::green));
			} else {
				painter.fillRect(start_x + i * perblock, strat_y + j * perblock, perblock, perblock, QBrush(Qt::black));
			}
		}
	}
}

首先判断绘图开关是否打开。
这里创建一个画笔对象用于绘制,我想使地图始终保持在frame的中央,所以计算了每个块的像素perblock,绘图开始的位置start_x与start_y,然后用painter的成员函数fillRect()对每个块分别涂色。
黑色为墙壁、边界,灰色为可通行路径,蓝色与绿色分别为起点和终点,红色是所在的位置,黄色是经过的路径。
然后使用两层循环涂色。

4.键盘按下事件虚函数

键盘按下时间将处理角色移动。

void mazeWidget::keyPressEvent(QKeyEvent* event) {
	if(!keybord_switch) return;
	int x = map->p_x;
	int y = map->p_y;
	//键盘移动逻辑:
	if(event->key() == Qt::Key_I || event->key() == Qt::Key_W) {
		if((*map)[x][y - 1] == 3 || (*map)[x][y - 1] == 4 || (*map)[x][y - 1] == 5 || (*map)[x][y - 1] == 6 || (*map)[x][y - 1] == 7) {
			map->p_y--;
		}
	} else if(event->key() == Qt::Key_K || event->key() == Qt::Key_S) {
		if((*map)[x][y + 1] == 3 || (*map)[x][y + 1] == 4 || (*map)[x][y + 1] == 5 || (*map)[x][y + 1] == 6 || (*map)[x][y + 1] == 7) {
			map->p_y++;
		}
	} else if(event->key() == Qt::Key_J || event->key() == Qt::Key_A) {
		if((*map)[x - 1][y] == 3 || (*map)[x - 1][y] == 4 || (*map)[x - 1][y] == 5 || (*map)[x - 1][y] == 6 || (*map)[x - 1][y] == 7) {
			map->p_x--;
		}
	} else if(event->key() == Qt::Key_L || event->key() == Qt::Key_D) {
		if((*map)[x + 1][y] == 3 || (*map)[x + 1][y] == 4 || (*map)[x + 1][y] == 5 || (*map)[x + 1][y] == 6 || (*map)[x + 1][y] == 7) {
			map->p_x++;
		}
	}
	//经过路径
	if((*map)[map->p_x][map->p_y] != 5 && (*map)[map->p_x][map->p_y] != 6)(*map)[map->p_x][map->p_y] = 7;
	repaint();
	//到达终点
	if((*map)[map->p_x][map->p_y] == 6) {
		map->makemap();
		repaint();
		grade += pow(map->getlevel(), 2);
		ui->grade_value->setText(QString::number(grade));
	}
}

这里使用传入参数event来判断按下的是什么按键,然后判断对应方向是否能进行移动,如果能,改变map的p_x与p_y。
同时经过的路径数值改为7,视作已经经过。
如果当前位置与终点重合,即生成下一轮地图,计分数为当前迷宫阶数的平方,并更新记分标签控件。

5.时间更新槽函数
void mazeWidget::time_update() {
	if(time != 0) {
		//计时中
		time--;
		ui->time_value->setText(QString::number(time));
		ui->progressBar->setValue(time / 2);
	} else {
		timer->stop();                          //停止计时器
		ui->progressBar->setVisible(false);     //隐藏进度条
		keybord_switch = false;                 //设置键盘响应、
		painting_switch = false;                //绘图响应、
		timing_switch = false;                  //计时响应为关闭状态
		repaint();                              //清除画布
		ui->start_btn->setEnabled(true);        //|
		ui->time_value->setText(" ");           //|
		ui->grade_value->setText(" ");          //|
		ui->stop_ptn->setEnabled(false);        //|设置各按钮与标签状态
		ui->end_btn->setEnabled(false);         //|
		ui->setting_btn->setEnabled(true);      //|
		//提示
		QMessageBox outgrade(QMessageBox::NoIcon, "恭喜", "您得分:" + QString::number(grade), QMessageBox::Ok);
		outgrade.exec();
		//分数重置
		grade = 0;
	}
}

计时器将在开始按钮按下时被设置为1000个周期,即一秒发出一次timeout()信号。
游戏共计时200秒,故进度条值为time / 2。同时计时停止时进行游戏停止的对应ui设置,并弹出消息窗口显示分数。

6.各按钮点击槽函数
void mazeWidget::on_start_btn_clicked() {
	painting_switch = true;
	timing_switch = true;
	keybord_switch = true;
	time = 200;
	timer->start(1000);
	ui->progressBar->setVisible(true);
	ui->progressBar->setValue(100);
	repaint();
	ui->time_value->setText(QString::number(time));
	ui->grade_value->setText(QString::number(grade));
	ui->start_btn->setEnabled(false);
	ui->stop_ptn->setEnabled(true);
	ui->end_btn->setEnabled(true);
	ui->setting_btn->setEnabled(false);
}
void mazeWidget::on_stop_ptn_clicked() {
	if(stop_switch) {
		timing_switch = false;
		keybord_switch = false;
		timer->stop();
		ui->stop_ptn->setText("继续");
		stop_switch = false;
	} else {
		timing_switch = true;
		keybord_switch = true;
		timer->start();
		ui->stop_ptn->setText("暂停");
		stop_switch = true;
	}
}
void mazeWidget::on_end_btn_clicked() {
	timing_switch = false;
	painting_switch = false;
	keybord_switch = false;
	stop_switch = false;
	timer->stop();
	time = 0;
	grade = 0;
	ui->progressBar->setVisible(false);
	ui->grade_value->setText(" ");
	ui->time_value->setText(" ");
	ui->stop_ptn->setText("暂停");
	ui->stop_ptn->setEnabled(false);
	ui->end_btn->setEnabled(false);
	ui->start_btn->setEnabled(true);
	ui->setting_btn->setEnabled(true);
	map->rebuildmap();
	repaint();
	
}
void mazeWidget::on_rule_btn_clicked() {
	QMessageBox rule(QMessageBox::NoIcon, "规则", "计时200秒,根据迷宫等级与经过关卡记分。\n操作方式:WASD或者IJKL控制方向。", QMessageBox::Ok);
	rule.exec();
}
void mazeWidget::on_setting_btn_clicked() {
	QStringList difficultys;
	difficultys << tr("小朋友启蒙难度(5阶迷宫)") << tr("简单难度(10阶迷宫)") << tr("普通难度(20阶迷宫)") << tr("困难难度(40阶迷宫)");
	QString difficulty = QInputDialog::getItem(this, tr("选择难度"),
	                     tr("请选择一个条目"), difficultys, 0, false);
	if(difficulty == tr("小朋友启蒙难度(5阶迷宫)")) {
		delete map;
		map = new maze(5);
		map->makemap();
	} else if(difficulty == tr("简单难度(10阶迷宫)")) {
		delete map;
		map = new maze(10);
		map->makemap();
	} else if(difficulty == tr("普通难度(20阶迷宫)")) {
		delete map;
		map = new maze(20);
		map->makemap();
	} else if(difficulty == tr("困难难度(40阶迷宫)")) {
		delete map;
		map = new maze(40);
		map->makemap();
	}
}
void mazeWidget::on_battle_btn_clicked() {
}
void mazeWidget::on_about_btn_clicked() {
	QMessageBox about(QMessageBox::NoIcon, "关于", "由amazcuter制作,代码请访问我的CSDN博客。\n玩得开心。\n\n双人对战模式未开发完成,将会在未来版本开放。", QMessageBox::Ok);
	about.exec();
}

分别进行对应的ui设置,功能调用。

step 4.主函数

#include "mazewidget.h"     //窗口类头文件
#include "maze.h"           //迷宫类头文件
#include <QApplication>
#include <QStyleFactory>    //界面风格头文件

int main(int argc, char* argv[]) {
    srand(time(NULL));                              //设置随机数种子
    QApplication a(argc, argv);
    a.setStyle(QStyleFactory::create("Fusion"));    //更改界面风格
    mazeWidget w;
    w.show();
    return a.exec();
}

这里加入头文件,设置一下界面风格和随机数种子。
双人模式放在之后更新吧,现在先放着。
好了现在项目就算编写好了。

成果:

成果
转载请表明出处哈,我还是很虚荣的233。
持续更新,我打算把这搞成我的实训作业,努力添加功能,优化外观。
当前版本v2.0。
觉得还行的看官点个攒再走吧…

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值