上一个帖子已经介绍了用uFun开发板制作的花里胡哨的“手柄”,这个帖子介绍一下贪吃蛇游戏界面的制作。两者之间采用串口通信,上个帖子介绍了数据发送的格式约定,这里就不在赘述了,详见帖子【uFun开发板评测】贪吃蛇(一):用uFun开发板做游戏手柄。
帖子内用的是开源版Qt,版本为Qt5.11.2
游戏界面及功能略为粗糙,文末提供了源码以及编译完成的程序下载附件。
![游戏界面.PNG 游戏界面.PNG](https://static.assets-stash.eet-china.com/forum/201906/05/122829exxmdh4xh6dmohkm.png)
- 首先实现串口通信是必须的。
- 绘制一条能动的贪吃蛇和一个食物是游戏的基础。
- 能判断游戏的结束,分为撞墙死和自闭而死。
- 试图模仿以前小霸王之类的游戏机,uFun做的手柄也能即插即用。
- 串口通信可以采用Qt5提供的QSerialPort类轻松实现;
- 为了能动,采用了定时器定时重绘的方法。每次重绘都更新一下贪吃蛇位置,这样贪吃蛇就动起来了;
- 贪吃蛇的躯体可以用一个数组来存储每一节身体的位置坐标,为了方便,采用了Qt自带的QList+QPoint实现;
- 食物被吃了之后需要随机出现,采用了QRandomGenerator随机数生成器来生成食物的坐标;
- 既然已经存储了贪吃蛇身体的坐标,场地的坐标范围也是已知的,只要将贪吃蛇下一个行进的坐标和它们比较就能知道游戏有没有结束;
- 为了实现即插即用,拔掉再插还能用,可以对QSerialPort类的errorOccurred信号进行处理,为了方便,只要串口出错就关闭串口。没有串口连接时就在游戏中不断尝试连接串口,遇到第一个能用的串口直接打开,之后停止尝试。(这里做了一个假设,就是按照常理游戏机上只会插手柄)
- 位置坐标并不是按像素算的。贪吃蛇的身体是由一个个小长方形构成的,长方形有一定的长和宽,位置坐标是以一个长方形为单位的,游戏界面的大小也是长方形的整数倍;
#define block_width 15 // 长方形的宽#define block_height 15 // 长方形的高 #define x_count 50 // 游戏界面的宽为(block_width*x_count) #define y_count 40 // 游戏界面的高为(block_height*y_count)
复制代码>>>我尝试着对实现贪吃蛇躯体做了个很不成熟的类封装,有些成员函数都是在编程过程中需要才加上的。
#ifndef SNAKE_H#define SNAKE_H #include <QPoint> #include <QList> // 宏定义四个方向 #define dir_up 1 #define dir_down 2 #define dir_left 3 #define dir_right 4 class Snake { public: Snake(QPoint headLocation, int lenth = 3, int cur_dir = dir_right); int getLenth(); // 获取贪吃蛇长度 int getCurDirecton(); // 获取当前前进方向 QList<QPoint>& getBody(); // 获取贪吃蛇的整个身体,主要在绘制里用 QPoint getHeadLocation(); // 获取贪吃蛇头部的坐标位置 bool addBlock(int curDir); // 添加一块身体 bool makeStep(int dir); // 向某个方向前进一步 bool isInSnake(QPoint& point); // 判断某一点是否在蛇的身体内 bool isInSnake(int x, int y); // 判断某一点是否在蛇的身体内 void tryGo(QPoint* point, int dir); // 尝试走一步,输入一个坐标和前进方向,获取走一步之后的新坐标点 private: int curDirection; // 用来记录当前前进的方向 QList<QPoint> snake_body; // 用来存储贪吃蛇的身体各单元坐标点 }; #endif // SNAKE_H
复制代码>>>串口初始化中主要需要连接处理串口的两个信号:
- 一个是出错信号errorOccurred,主要做的工作是把出错时已经打开的串口关闭,这样程序循环内才会继续尝试打开串口。(在初始化时也会有一次尝试寻找可用的串口);
- 另一个是接收到数据的信号readyRead,其中的数据处理思路和uFun开发板处理思路相同,不再赘述。当接收到信息之后,会把方向暂存在一个变量里,只有当定时器到点重绘时才会真正更新前进方向,也就是说只认重绘前最后一次方向。
// 帧信息处理过程if(ch == '>'){ // 一帧接收结束 // 当停在游戏开始界面时,检测到按键就直接置位开始游戏 if(this->startFlag == false){ this->startFlag = true; if(pTimer->isActive() == false) pTimer->start(); return; } // A-left D-right W-up S-down switch (rcvData.toUtf8().at(0)) { case 'A': case 'a': if(this->curDirection != dir_right) this->preDirection = dir_left; break; case 'D': case 'd': if(this->curDirection != dir_left) this->preDirection = dir_right; break; case 'W': case 'w': if(this->curDirection != dir_down) this->preDirection = dir_up; break; case 'S': case 's': if(this->curDirection != dir_up) this->preDirection = dir_down; break; } frameFlag = false; }
复制代码void Widget::makeFood(){ QRandomGenerator generator; quint32 x, y; while(true){ generator.seed(static_cast<quint32>(QTime::currentTime().second())); x = generator.generate(); y = generator.generate(); x %= x_count; y %= y_count; if(pSnake->isInSnake(static_cast<int>(x), static_cast<int>(y)) == false) break; } foodPoint.setX(static_cast<int>(x)); foodPoint.setY(static_cast<int>(y)); DEBUG_cout(x << y) ; }
复制代码pTimer = new QTimer(this); pTimer->setInterval(150); // 控制重绘的时间间隔,可以控制游戏难度 connect(pTimer, &QTimer::timeout, this, [=](){ // 如果串口没有连接成功,则一直尝试 if(pSerialPort->isOpen() == false){ DEBUG_cout("still try"); QList<QSerialPortInfo> infoList = QSerialPortInfo::availablePorts(); // 如果连接上一个串口直接跳出,没有可用串口会在程序中不断循环查找串口并尝试连接 QSerialPortInfo info; foreach(info, infoList){ pSerialPort->setPortName(info.portName()); if(pSerialPort->open(QIODevice::ReadWrite)) break; } } if(this->startFlag == true){ // 只有每次绘制的时候才会真正修改蛇的前进方向 this->curDirection = this->preDirection; // 判断是否增加长度以及是否游戏结束 if(this->isGameOver()){ // 游戏结束后清除标志变量,以进行下一场游戏 this->startFlag = false; pSnake = new Snake(QPoint(x_count / 2, y_count / 2), 5, curDirection); makeFood(); } else { QPoint point = pSnake->getHeadLocation(); pSnake->tryGo(&point, this->curDirection); // 尝试前进一步 if(point == foodPoint){ // 判断有没有吃到食物 pSerialPort->write("<F>"); // 发送<F>表示吃到食物 pSnake->addBlock(this->curDirection); // 更新食物的位置 this->makeFood(); } else{ pSnake->makeStep(curDirection); } } this->update(); } }); pTimer->start();
复制代码void Widget::paintEvent(QPaintEvent *event){ QPainter painter(this); painter.setBrush(QColor("#ff0000")); QList<QPoint> snake_body = pSnake->getBody(); QPoint point; foreach(point, snake_body){ painter.drawRect(point.x() * block_width, point.y() * block_height, block_width, block_height); } painter.drawEllipse(foodPoint.x() * block_width, foodPoint.y() * block_height, block_width, block_height); if(this->startFlag == false){ QFont font = painter.font(); font.setPixelSize(block_height * 3); painter.setFont(font); painter.drawText(this->rect(), Qt::AlignCenter, "按任意键开始游戏"); } }
复制代码PS:程序内也实现了keyPressEvent函数,也就是说键盘的上下左右和WASD也能控制程序,Q键退出程序。
Qt项目源码:
![](static/image/filetype/zip.gif)
2019-6-5 13:34 上传
点击文件名下载附件
源码
下载即用的程序(Snake.exe):
![](static/image/filetype/zip.gif)
2019-6-5 13:36 上传
点击文件名下载附件
程序
全部回复 7
- 69 主题
- 144 帖子
- 2275 积分
身份:版主
E币:2335
发消息
whik
![技术达人 技术达人](static/image/common/medal4.gif)
![技术高手 技术高手](static/image/common/medal5.gif)
发表于2019-6-6 11:15:47
显示全部楼层
沙发
这个太厉害吧!学习一下Qt的实现方法
>>资料:UFU学习笔记4.OLED响应触摸按键源程序
- 支持
- 反对
- 举报
回复
whik 发表于 2019-6-6 11:26
QRandomGenerator报错:widget.cpp:6: error: QRandomGenerator: No such file or directory
Qt版本:5.8
Q ...
刚看到,这可能是安装Qt时没选择这个模块,你可以换用qsrand()和qrand()函数,这两个应该会有。具体把widget.cpp内makeFood()函数改为以下内容:
int x, y; while(true){ qsrand(static_cast<uint>(QTime::currentTime().second())); x = qrand(); y = qrand(); x %= x_count; y %= y_count; if(pSnake->isInSnake(x, y) == false) break; } foodPoint.setX(x); foodPoint.setY(y);
复制代码![](/static/image/mianbaoban/2.0/ad.png)
![](/static/image/mianbaoban/2.0/ad.png)
评测文章
热帖
大家都在看的技术资料
举报
内容系网友发布,其中涉及到安全隐患的内容系网友个人行为,不代表面包板社区观点
关闭
站长推荐
/3
![上一条 上一条](static/image/common/pic_nv_prev.gif)
![下一条 下一条](static/image/common/pic_nv_next.gif)
-
返回顶部
-
工具栏