200+行代码写一个简易的Qt界面贪吃蛇

news2024/11/19 0:21:27

照例先演示一下:

 一个简单的Qt贪吃蛇,所有的图片都是我自己画的(得意)

大致的运行逻辑和之前那个200行写一个C++小黑窗贪吃蛇差不多,因此在写这个项目的时候,大多情况是在想怎么通过Qt给展现出来。

背景图片:

由于背景图片是自己画的,因此大小是刚刚好符合我设定的主窗口大小的,直接是把背景图放大两倍,然后放在坐标为0,0的位置上,下面这段代码写在绘画事件的函数里,窗口初始化的时候系统会自动调用绘画事件,因此我们不用手动去调用就可以实现背景图片的渲染。

    QPainter* paint = new QPainter(this);
    //画背景,可以修改为可选背景,感兴趣可以自己去搞一下
    QPixmap background;
    background.load(":/image/background.png");
    background=background.scaled(background.width()*2, background.height()*2);
    paint->drawPixmap(0,0,background);

 那么除了背景图片以外,还有要画的就是蛇和食物了(这里的食物我画了我最喜欢的水果——芭乐,应该看得出来叭),所以在主窗口里需要有对应的蛇和食物的类,并且这些类里需要有它们所在位置的坐标,然后再绘画事件里根据它们的坐标来绘制。

画食物比较简单,直接在食物的坐标上绘制就行。

有界面的贪吃蛇和小黑窗不一样,我们需要展示出蛇的移动方向,也就是说不同的移动方向,蛇头 应该看起来是不一样的,所以在绘画蛇头的时候我们需要判断蛇的移动方向,以此来决定蛇头应该用哪个图片。(直接旋转图片有些麻烦,所以我直接画了四个方向的蛇头图片)。

蛇身子应该也是要根据移动的情况而绘制不同的图片的,但是逻辑太复杂了,不仅要考虑蛇的每一节身子是上下方向的还是左右方向的,甚至有些身子是弯曲的转向的,所以我直接投降了,把蛇的身子画成圆乎乎的,这样就分辨不出什么方向什么的了,统一用同一个图片就好啦。

蛇尾和蛇头类型,也是要根据具体情况来绘制不同的图片,不过和蛇头不一样的是,蛇尾的图片不取决与蛇的移动方向,因为蛇尾具有滞后性,所以判断的依据是蛇的最后一节身子。

if (isBegin) {  //当游戏开始时才画蛇和食物
        //画食物
        QPixmap bale;
        bale.load(":/image/bale.png");
        paint->drawPixmap(food[0],food[1],bale);
        //画蛇头
        QPixmap snakeHead;
        //根据蛇移动的方位来改变蛇头的图片
        if(greedysnake.direction=='r')snakeHead.load(":/image/head(right).png");    
        else if(greedysnake.direction=='l')snakeHead.load(":/image/head(left).png");
        else if(greedysnake.direction=='u')snakeHead.load(":/image/head(up).png");
        else snakeHead.load(":/image/head(down).png");
        paint->drawPixmap(greedysnake.head[0], greedysnake.head[1], snakeHead);
        //画蛇身
        for (auto& body : greedysnake.body) {
            QPixmap snakeBody;
            snakeBody.load(":/image/body.png");
            paint->drawPixmap(body[0], body[1], snakeBody);
        }
        //画蛇尾
        QPixmap snakeTail;
        vector<int>lastbody = *(greedysnake.body.end() - 1);
        //根据蛇的最后一节身子来改变蛇尾的图片
        if (greedysnake.tail[0] == lastbody[0]) {
            if (greedysnake.tail[1] > lastbody[1]) snakeTail.load(":/image/tail(up).png");
            else snakeTail.load(":/image/tail(down).png");
        }
        else if (greedysnake.tail[1] == lastbody[1]) {
            if (greedysnake.tail[0] > lastbody[0]) snakeTail.load(":/image/tail(left).png");
            else snakeTail.load(":/image/tail(right).png");
        }
        paint->drawPixmap(greedysnake.tail[0],greedysnake.tail[1],snakeTail);
    }

生成食物:

生成食物的时间点在游戏刚开始的时候,以及蛇吃掉食物以后。

生成食物其实就是更新食物的坐标,我们使用随机数去生成,并且我这边设置的是食物大小是50*50像素的,并且蛇的每一节身子都是50*50像素的,所以我们在生成坐标的时候,需要保证生成出来的坐标是50的倍数(包括蛇自身的坐标)。

并且生成出来的坐标不能在蛇身上。

void MainUI::createFood(){
    //生成食物
    std::uniform_int_distribution<int>uw(0,1300/50-1);
    std::uniform_int_distribution<int>uh(1,800/50-1);
    bool flag = true;
    while (1) {
        flag = true;
        int x = uw(e)*50; int y = uh(e)*50;
        //不能生成在蛇头的位置
        if (x == greedysnake.head[0] && y == greedysnake.head[1]) continue;
        //不能生成在蛇尾的位置
        if (x == greedysnake.tail[0] && y == greedysnake.tail[1]) continue;
        //不能生成在蛇身的位置
        for (const auto &body : greedysnake.body) {
            if (body[0] == x && body[1] == y) {
                flag = false;
                break;
            }
        }
        if (flag) {
            food[0] = x, food[1] = y;
            qDebug() << x << ' ' << y << endl;
            break;
        }
    }
}

让蛇动起来:

Qt有定时器,这是对比小黑窗来说比较方便的一个点,我们可以通过定时器,每个一段时间就更新蛇的坐标,然后再更新绘图,以此来达到让蛇移动的效果。

每次移动蛇,我们就把蛇头的坐标按照移动的方向来做出改变。而蛇的身子要达到移动的效果则是把最后一节身子的坐标从身子里删去,然后在存放身子的容器的开头加上移动前蛇头的坐标,这样蛇的身子也就可以达到移动的效果了。

而蛇的尾部只需要更新成移动前蛇的最后一节身子的坐标即可。

在移动的时候我们还应该有个判断,如果吃到了芭乐,即新蛇头的坐标等于芭乐的坐标,那么身子加长,并且重新生成新的芭乐。加长身子是将移动前头的坐标加入到蛇身子的容器的开头,而芭乐的坐标成为新的蛇头的坐标,这样就达到了增长的效果。

除了判断食物,我们还应该判断如果吃到了自己的身体,我们就应该弹出一个消息提示框,可以选择是否重新开始一局游戏。

如果蛇移动移出了边界,那么可以判断是失败了,而我这里的处理是可以穿越到界面的另一侧继续游戏,这样降低了难度,增加了可玩性。

void MainUI::timerEvent(QTimerEvent* e){        //定时器
    if (isBegin) {  //如果开始游戏
        //蛇的移动
        greedysnake.body.insert(greedysnake.body.begin(), greedysnake.head);
        greedysnake.tail = *(greedysnake.body.end() - 1);
        greedysnake.body.pop_back();
        if (greedysnake.direction == 'r')   greedysnake.head[0] += greedysnake.speed;
        else if (greedysnake.direction == 'l') greedysnake.head[0] -= greedysnake.speed;
        else if (greedysnake.direction == 'u') greedysnake.head[1] -= greedysnake.speed;
        else greedysnake.head[1] += greedysnake.speed;
        for (auto& body : greedysnake.body) {
            //如果碰到身体
            if (body[0] == greedysnake.head[0] && body[1] == greedysnake.head[1]) {
                isBegin = false;
                killTimer(TimerID);
                QMessageBox::StandardButton ans = QMessageBox::question(this, "game over", QString::fromLocal8Bit("你吃到了自己,游戏结束,是否在来一把"));
                if (ans == QMessageBox::No) {
                    exit(0);
                }
                else {
                    //手动初始化一下蛇
                    greedysnake.head = { 650,350 };
                    greedysnake.body = {{ 600, 350 }};
                    greedysnake.tail = { 550,350 };
                    beginGame();
                }
                return;
            }
        }
        if (greedysnake.head[0] == food[0] && greedysnake.head[1] == food[1]) {
            //如果吃到食物,身体变长并且重新生成食物.
            greedysnake.body.insert(greedysnake.body.begin(), greedysnake.head);
            if (greedysnake.direction == 'r')   greedysnake.head[0] += greedysnake.speed;
            else if (greedysnake.direction == 'l') greedysnake.head[0] -= greedysnake.speed;
            else if (greedysnake.direction == 'u') greedysnake.head[1] -= greedysnake.speed;
            else greedysnake.head[1] += greedysnake.speed;
            //增加难度,加快速度,不要也可以
            if (greedysnake.body.size() > 5) { 
                killTimer(TimerID);
                TimerID = startTimer(200);  //通过改变调用定时器的频率来达到改变蛇的速度
            }
            else if (greedysnake.body.size() > 10) {
                killTimer(TimerID);
                TimerID = startTimer(180);
            }
            else if (greedysnake.body.size() > 15) {
                killTimer(TimerID);
                TimerID = startTimer(150);
            }
            else if (greedysnake.body.size() > 20) {
                killTimer(TimerID);
                TimerID = startTimer(120);
            }
            else if (greedysnake.body.size() > 25) {
                killTimer(TimerID);
                TimerID = startTimer(100);
            }
            createFood();
        }
        //全屏移动,即可以从下面穿过,到达上面
        if (greedysnake.head[0] < 0) greedysnake.head[0] = 1250;
        if (greedysnake.head[0] > 1300)greedysnake.head[0] = 0;
        if (greedysnake.head[1] >= 800) greedysnake.head[1] = 50;
        if (greedysnake.head[1] <= 0) greedysnake.head[1] = 800;
        update();   //更新绘图
    }
}

按键事件:

主要的核心功能就在上面了,我这里最后补充一个按键操控蛇的点。

我们可以通过重写窗口的按键事件来获取到玩家按下的按钮。

通过e->key()来获取一个整型数据,每个按钮都对应一个数值,我一个个把按键试出来了,然后写了个逻辑,如果在一开始的时候,我们除了可以通过鼠标点击来开始游戏,也可以通过按下回车键来开始,另外也可以按下空格来开始游戏,但是实际上我没有写关于空格的逻辑,但是它就是可以通过空格来触发那个按钮。

另外在游戏中可以按下空格来暂停游戏。

值得一提的是在改变蛇的移动方向的时候,需要注意不能直接180°转弯。

void MainUI::keyPressEvent(QKeyEvent* e){
    int nowKey = e->key();
    if (nowKey == 16777220) { //回车键
        if(begin!=nullptr) begin->click();
    }
    if (isBegin) {
        //修改运动方向,注意不能直接180°转弯
        if ((nowKey == 87 || nowKey == 16777235) && greedysnake.direction != 'd') greedysnake.direction = 'u';
        else if ((nowKey == 83 || nowKey == 16777237) && greedysnake.direction != 'u') greedysnake.direction = 'd';
        else if ((nowKey == 65 || nowKey == 16777234) && greedysnake.direction != 'r') greedysnake.direction = 'l';
        else if ((nowKey == 68 || nowKey == 16777236) && greedysnake.direction != 'l')greedysnake.direction = 'r';
        else if (nowKey == 32) isBegin=false;
    }else {
        if (nowKey == 32) isBegin = true;
    }
}

小结:

我的评价是不管是学习什么技术,最好的练手项目就是写一个贪吃蛇,之前学C,java,python,C++的时候都写了贪吃蛇,而现在学了qt也写了个贪吃蛇,这对于巩固基础有着非常好的效果。

想要源码的小伙伴可以直接在我CSDN的主页里找到对应的资源免费下载,我已经上传到SCDN了,也可以关注我的微信公众号:折途想要敲代码,回复关键词“qt贪吃蛇”领取完整的源码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/813541.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Minio中间件】上传图片并Vue回显

流程&#xff1a; 目录 1.文件服务器Minio的安装 1.1 下载Minio安装后&#xff0c;新建1个data文件夹。并在该安装目录cmd 敲命令。注意不要进错目录。依次输入 1.2 登录Minio网页端 1.3 先建1个桶&#xff08;buckets&#xff09;&#xff0c;点击create a bucket 2. Spr…

使用docker部署springboot微服务项目

文章目录 1. 环境准备1. 准备好所用jar包项目2.编写相应的Dockerfile文件3.构建镜像4. 运行镜像5. 测试服务是否OK6.端口说明7.进入容器内8. 操作容器的常用命令 1. 环境准备 检查docker是否已安装 [rootlocalhost /]# docker -v Docker version 1.13.1, build 7d71120/1.13.…

LabVIEW开发航天器动力学与控制仿真系统

LabVIEW开发航天器动力学与控制仿真系统 计算机仿真是工程设计和验证的非常有用的工具。它节省了大量的时间、金钱和精力。航天器动力学与控制仿真系统由LabVIEW程序开发&#xff0c;它是模拟航天器等动态系统的有用工具。还可轻松与硬件连接并输出真实信号。 项目采用系统工…

《Kali渗透基础》11. 无线渗透(一)

kali渗透 1&#xff1a;无线技术特点2&#xff1a;IEEE 802.11 标准2.1&#xff1a;无线网络分层2.2&#xff1a;IEEE2.3&#xff1a;日常使用标准2.3.1&#xff1a;802.112.3.2&#xff1a;802.11b2.3.3&#xff1a;802.11a2.3.4&#xff1a;802.11g2.3.5&#xff1a;802.11n …

一维(1D)CNN模型下轴承故障诊断(Python,TensorFlow框架下,很容易改为其它模型,解压缩后可以直接运行,无需修改任何目录)

1.数据集 使用凯斯西储大学轴承数据集&#xff0c;一共有4种负载下采集的数据&#xff0c;每种负载下有10种 故障状态&#xff1a;三种不同尺寸下的内圈故障、三种不同尺寸下的外圈故障、三种不同尺寸下的滚动体故障和一种正常状态。 2.模型&#xff08;1DCNN&#xff09; 使…

【Git】git reset 版本回退 git rm

前言 在日常开发时&#xff0c;我们经常会需要撤销之前的一些修改内容或者回退到之前的某一个版本&#xff0c;这时候reset命令就派上用场了 git reset 用法1——所有文件回退到某个版本 1、使用git reflog查看要回退的commit对象 2、使用git reset [-- hard/soft /mixed] …

26 用lsqnonlin求解最小二乘问题(matlab程序)

1.简述 函数语法 x lsqnonlin(fun,x0) 函数用于&#xff1a; 解决非线性最小二乘(非线性数据拟合)问题 解决非线性最小二乘曲线拟合问题的形式 变量x的约束上下限为ub和lb&#xff0c; x lsqnonlin(fun,x0)从x0点开始&#xff0c;找到fun中描述的函数的最小平方和。函数fu…

【前端知识】React 基础巩固(三十九)——React-Router的基本使用

React 基础巩固(三十九)——React-Router的基本使用 一、Router的基本使用 Router中包含了对路径改变的监听&#xff0c;并且会将相应的路径传递给子组件。 Router包括两个API&#xff1a; BrowserRouter使用history模式 HashRouter使用hash模式&#xff08;路径后面带有#号…

Debeizum 增量快照

在Debeizum1.6版本发布之后&#xff0c;成功推出了Incremental Snapshot&#xff08;增量快照&#xff09;的功能&#xff0c;同时取代了原有的实验性的Parallel Snapshot&#xff08;并行快照&#xff09;。在本篇博客中&#xff0c;我将介绍全新快照方式的原理&#xff0c;以…

S32K14x FlexNVM介绍(flexible Non-volatile memory)

S32K14x是一款NXP推出的32位汽车级微控制器&#xff0c;其存储结构相对复杂。下面是对其存储结构的中文介绍&#xff1a; S32K14x采用了分层存储结构&#xff0c;包括Flash存储器和SRAM存储器。Flash存储器用于存储程序代码和常量数据&#xff0c;而SRAM存储器用于存储变量数据…

常见的几种排序

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C &#x1f525;座右铭&#xff1a;“不要等到什么都没有了&#xff0c;才下…

【分布式系统】分布式系统的8个谬误

网络可靠 对于分布式系统来说&#xff0c;网络、计算、存储是三大基石&#xff0c;系统之间进行拆分隔离之后&#xff0c;那么必定存在网络通讯&#xff0c;而网络是最不可靠的。 不管是从硬件层面还是软件层面来说&#xff0c;网络是不可靠的。&#xff08;断电、配置错误、ID…

ChatGPT结合知识图谱构建医疗问答应用 (一) - 构建知识图谱

一、ChatGPT结合知识图谱 在本专栏的前面文章中构建 ChatGPT 本地知识库问答应用&#xff0c;都是基于词向量检索 Embedding 嵌入的方式实现的&#xff0c;在传统的问答领域中&#xff0c;一般知识源采用知识图谱来进行构建&#xff0c;但基于知识图谱的问答对于自然语言的处理…

《JavaSE-第二十一章》之线程的状态与中断

前言 在你立足处深挖下去,就会有泉水涌出!别管蒙昧者们叫嚷:“下边永远是地狱!” 博客主页&#xff1a;KC老衲爱尼姑的博客主页 博主的github&#xff0c;平常所写代码皆在于此 共勉&#xff1a;talk is cheap, show me the code 作者是爪哇岛的新手&#xff0c;水平很有限&…

Ctfshow web入门 sqli-labs特性篇 web517-web568 详细题解 全

web517 输入?id1 正常 输入?id1 报错 .0 输入?id1-- 正常判断是字符型注入&#xff0c;闭合方式是这里插一句。limit 100,1是从第100条数据开始&#xff0c;读取1条数据。limit 6是读取前6条数据。 ?id1 order by 3-- 正常判断回显位有三个。?id1 and 12 union se…

json-server详解

零、文章目录 json-server详解 1、简介 Json-server 是一个零代码快速搭建本地 RESTful API 的工具。它使用 JSON 文件作为数据源&#xff0c;并提供了一组简单的路由和端点&#xff0c;可以模拟后端服务器的行为。github地址&#xff1a;https://github.com/typicode/json-…

RWEQ模型——土壤风蚀模拟

详情点击链接&#xff1a;基于“RWEQ”集成技术在土壤风蚀模拟与风蚀模数估算、变化归因分析中的实践应用及SCI论文撰写 前沿 土壤风蚀是一个全球性的环境问题。中国是世界上受土壤风蚀危害最严重的国家之一&#xff0c;土壤风蚀是中国干旱、半干旱及部分湿润地区土地荒漠化的…

解读Spring-context的property-placeholder

在spring中&#xff0c;如果要给程序定义一些参数&#xff0c;可以放在application.properties中&#xff0c;通过<context:property-placeholder>加载这个属性文件&#xff0c;然后就可以通过value给我们的变量自动赋值&#xff0c;如果你们的程序可能运行在多个环境中&…

Android 面试题 应用程序结构 九

&#x1f525; 核心应用程序 Activity五个状态&#x1f525; Starting-> running-> paused-> stopped-> killed 启动状态&#xff08;Starting&#xff09;&#xff1a;Activity的启动状态很短暂&#xff0c;当Activity启动后便会进入运行状态&#xff08;Running…

大数据Flink(五十四):Flink用武之地

文章目录 Flink用武之地 一、Event-driven Applications【事件驱动】 二、Data Analytics Applications【数据分析】 三、​​​​​​​Data Pipeline Applications【数据管道】 Flink用武之地 应用场景 | Apache Flink 从很多公司的应用案例发现&#xff0c;其实Flink主…