news 2026/9/23 18:55:07

Linux环境下用Qt与C++开发“别踩白块儿”小游戏

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux环境下用Qt与C++开发“别踩白块儿”小游戏

简介:基于Linux、Qt与C++开发的“别踩白块儿”小游戏完整工程源码,面向有一定C++或Qt基础、希望将面向对象思想和常用容器应用到实际游戏项目中的学习者。项目使用工厂模式创建黑块与白块,以queue容器保存方块序列;每次生成行时调用带时间种子的qrand随机函数,用对4取余的结果决定黑块所在列。游戏内置30秒倒计时、当前得分与历史最高分记录,交互流程覆盖开始界面、游戏主视图和结束反馈。压缩包共52个文件,主要包括6个cpp源文件、5个头文件、2个ui界面文件与1个pro工程文件,另含34张png图片、2张jpg背景图和1个qrc资源文件,整体仅736KB,目录中图片素材与代码模块对应清晰,便于打开工程直接阅读和二次开发。已有1182人学习下载。阅读源码可理解Qt定时器、信号槽、随机数生成、工厂模式及队列数据结构在游戏开发中的综合运用,并可在现有框架上扩展难度、音效或排行榜。

1. 在 Linux 上从零写一个“别踩白块儿”:为什么是 QT + C++,而不是 Web 或 Python

如果你是个刚接触 Linux 的 C++ 学习者,想找一个能同时练到“界面绘制、事件响应、定时器、数据结构”的小项目,“基于 Linux、QT、C++ 的别踩白块儿小游戏”几乎是绕不开的选择。它的难度刚好卡在“入门之上、进阶未满”:不需要处理复杂的网络和并发,但足以让你把 QT 的事件循环、QPainter 绘图、QTimer 驱动动画这些核心机制全部摸一遍。更关键的是,你最终拿到的不是黑窗口里的命令行程序,而是一个带图形界面、能发布到别的 Linux 机器上跑起来的桌面应用——这对简历和实际开发手感都是实打实的加分项。

我见过不少人拿 Python 的 pygame 或网页版 JS 实现类似游戏,但坦白说,在“运行环境受限的国产 Linux 桌面”上,QT 的跨发行版兼容性和 C++ 的性能余量才是真实场景里最省心的组合。这篇文章我会从环境搭建、核心逻辑、绘图交互一路讲到发布打包和踩坑记录,全程给可复现代码,目的是让你在周末两天内把它跑起来并看懂每一行在干什么。

2. 先把地基打牢:Linux 下 QT + C++ 环境搭建与最小项目跑通

2.1 装对 Qt 版本:apt 源安装还是官网下载

在 Linux 上装 QT,第一道坎是“选哪个版本、用什么方式装”。常见的做法有两种:一是用发行版自带的软件源安装,比如 Ubuntu 上的apt install qt5-default qtcreator;二是从 QT 官网下载在线安装器,选指定的套件安装。我的建议是:如果你只是为了跑这个小游戏,优先用软件源里的版本

因为软件源里的 Qt 已经针对当前发行版做完了编译匹配,依赖装得少。官网安装器虽然版本新,但容易和系统自带的库冲突,特别是后面要在无桌面环境里跑的时候,平台插件路径不对会直接让你看到qt.qpa.plugin: could not find the qt platform plugin "linuxfb"这类报错。下面是我实际用的安装命令:

sudo apt update sudo apt install qtbase5-dev qt5-qmake qtcreator g++ libgl1-mesa-dev

qtbase5-dev是核心开发包,包含 Widgets、Gui、Core 这些基础模块;qt5-qmake提供构建工具;libgl1-mesa-dev是 OpenGL 相关的依赖,QT 的窗口渲染会用得到。装完之后检查一下版本:

qmake --version g++ --version

能看到输出里出现 QMake version 和 g++ 版本号,说明工具链通了。这一步最常翻车的是qt5-default这个包在新版本 Ubuntu 里已经不存在,所以直接装qtbase5-dev更稳妥。

2.2 用 qmake 还是 CMake:单文件小游戏我用 qmake

关于构建系统,很多人一上来就纠结。CMake 是行业主流,但 QMake 对单个 .pro 工程的配置极其简单。这个项目规模不大,我建议你用 qmake,因为它能让你把精力放在 C++ 和 QT 的逻辑上,而不是在find_packagetarget_link_libraries里消耗热情。

工程文件whiteblock.pro长这样:

QT += core gui widgets TARGET = whiteblock TEMPLATE = app SOURCES += \ main.cpp \ gameboard.cpp HEADERS += \ gameboard.h

QT += core gui widgets声明了依赖模块,注意必须写widgets,否则 QApplication 和 QWidget 相关类都找不到。TARGET指定生成的可执行文件名,TEMPLATE = app表明这是一个应用程序而不是库。加新文件时往 SOURCES 和 HEADERS 里追加就行,qmake 会自动处理头文件依赖关系。

2.3 跑通第一个最小窗口:main.cpp 与空白画布

现在写一个最简的main.cpp来验证整个链路:

#include <QApplication> #include <QWidget> int main(int argc, char *argv[]) { QApplication app(argc, argv); QWidget window; window.setWindowTitle("别踩白块儿 - 准备中"); window.resize(400, 600); window.show(); return app.exec(); }

这段代码做的只有三件事:创建 QApplication 对象、创建主窗口并设置标题和尺寸、进入事件循环。app.exec()会阻塞在这里,直到窗口被关闭。在终端里执行qmake && make,然后./whiteblock,如果弹出一个 400x600 的空白窗口,恭喜,Qt 的最小运行链路已经打通。

提示:如果你用的是 Wayland 桌面,可能还需要装qtwayland5插件,否则 QT 默认走 xcb 会失败。这是环境差异导致的,和代码无关。

3. 写游戏核心逻辑:方块生成、下落节奏与点击判定(C++ 核心)

3.1 数据结构设计:为什么不直接用 QList 存方块

在“别踩白块儿”里,核心数据是一个 4 列、无限纵向延伸的板子。每一行有且只有一个黑块,位置随机。玩家要赶在黑块滚出屏幕之前把它点掉。常见的数据结构是二维数组或 QList,但我建议把“一行的四个方块”封装成一个结构体,代码可读性会好很多:

struct Row { int blackIndex; // 0~3 表示本行哪个是黑块 bool clicked; // 是否已被点击 bool passed; // 是否已滚动出可视区 };

这里blackIndex用 int 而不是 bool,是因为后面扩展难度时,可能出现一行多个黑块或者长条黑块(占两格)的需求,int 的可扩展性更好。整个游戏板就是一个QVector<Row>,每帧按速度向下移动,视觉上就是白块往上走。

3.2 方块下落逻辑:QTimer 驱动与帧间隔控制

游戏的核心循环由 QTimer 驱动。每触发一次定时器,把所有方块的 y 坐标往下移一段距离,然后触发重绘。关键参数有三个:定时器间隔、每次移动的像素数、方块高度。我用的参数是:

const int COLUMN_COUNT = 4; const int ROW_HEIGHT = 150; // 每行高 150 像素 const int MOVE_SPEED = 3; // 每帧下移 3 像素 const int TIMER_INTERVAL = 16; // 约 60FPS

定时器间隔设 16ms,配合 3px 的位移,下落速度就是 180px/s,约 1.2 秒滚完一行。这个速度起步合适,后面可以在游戏分数过 20 时把MOVE_SPEED提到 5。我见过有人把间隔设成 10ms 以下,结果在低端嵌入式板子上 CPU 占用直接打满,界面反而卡得更厉害,得不偿失。

定时器的实现在GameBoard类里:

void GameBoard::startGame() { m_timer = new QTimer(this); m_timer->setInterval(TIMER_INTERVAL); connect(m_timer, &QTimer::timeout, this, &GameBoard::onTick); m_timer->start(); } void GameBoard::onTick() { for (int i = m_rows.size() - 1; i >= 0; --i) { m_rows[i].y += MOVE_SPEED; } generateNewRowIfNeeded(); removeRowsOutOfView(); update(); // 触发 paintEvent 重绘 }

onTick里先移动所有行,再补新行、删掉滚出底部的行,最后调update()。注意不要直接调用repaint()update()会合并同一帧内的多次请求,避免频繁刷新导致的闪烁。捕新行的条件很简单:当前最上面一行已经进入可视区,就在顶部补一行新的。

3.3 点击判定:黑块命中、白块误触与漏点漏判

点击判定是整个游戏最容易写出 bug 的地方。我建议统一走“坐标换算”的方式,而不是去比对控件。鼠标点击的坐标先相对窗口左上角换算成游戏区域的行号与列号,再查这一行这一列是不是黑块:

void GameBoard::mousePressEvent(QMouseEvent *event) { if (m_gameOver) return; QPoint pos = event->pos(); int col = pos.x() * COLUMN_COUNT / width(); int rowIndex = findRowIndexAtY(pos.y()); if (rowIndex < 0) return; // 点到了顶部的空白过渡区 Row &row = m_rows[rowIndex]; if (col == row.blackIndex) { row.clicked = true; m_score++; emit scoreChanged(m_score); } else { gameOver(); // 点到白块,直接结束 } }

findRowIndexAtY的职责是根据 y 坐标反推行号:return (pos.y() - topOffset) / ROW_HEIGHT;。这里要特别注意topOffset,可视区域顶部可能有一段留白,不参与行号计算,不然点到留白区域会被误判成第一行,产生“明明没点到白块却 Game Over 的玄学”。

漏点判断放在行滚出屏幕的时候:

void GameBoard::handleRowLeavingView(Row &row) { if (!row.clicked && !row.passed) { row.passed = true; m_missedCount++; if (m_missedCount >= 3) { gameOver(); } } }

这里设计成漏掉 3 个黑块才结束,而不是漏一个就结束,因为摸过的节奏游戏都知道,“手滑点空”是常态,连续 3 次漏点才是真的跟不上。

4. 用 QT 把画面画出来:QPainter 绘图、鼠标事件与界面刷新

4.1 为什么用 QPainter 绘制而不是堆 QLabel

很多新手拿到这个项目会想到用 QGridLayout 加 QLabel 铺出一个 4xN 的棋盘,然后改背景色模拟方块移动。这个思路在视觉上没问题,但性能和开发效率都很差:每次下落都要移动几十个控件、触发多次布局计算,而且把逻辑和界面高度耦合,后面想加动画或者变形,改起来想死。

更干净的做法是用 QPainter 在paintEvent里一次性把整个画面画出来。绘制逻辑非常直白:

void GameBoard::paintEvent(QPaintEvent *) { QPainter painter(this); painter.fillRect(rect(), Qt::white); for (const Row &row : m_rows) { int x = row.blackIndex * (width() / COLUMN_COUNT); int y = row.y; int w = width() / COLUMN_COUNT; int h = ROW_HEIGHT; if (row.clicked) { painter.fillRect(x, y, w, h, QColor(80, 80, 80)); // 已点击的方块变灰 } else { painter.fillRect(x, y, w, h, Qt::black); } } }

painter.fillRect是填色,Qt::black就是黑块。已点击的方块置灰,能给玩家“这格已经处理过了”的正反馈。注意绘制顺序:因为白块是背景色,只需要画黑块;每行一个黑块,整行铺白色背景,视觉上就形成了黑块在白色画布上往下掉的效果。这里的 width() 是窗口实际宽度,除以 4 得列宽,窗口拉伸时方块会自动自适应,非常方便。

4.2 鼠标事件:重写 mousePressEvent 与事件过滤器的取舍

鼠标点击有两种接法。第一种是直接重写GameBoard::mousePressEvent,适合事件处理逻辑比较单一的场景,我上面的代码就是这么写的。第二种是安装事件过滤器installEventFilter,适合你要同时监听多个子控件事件的场景。小游戏玩家只会在主窗口上点击,没有子控件抢事件,所以重写 + 坐标换算是最短路径。如果你后面想加“点击特效动画层”,可以把这个动画层作为子控件,然后在父窗口装事件过滤器统一转发,那是后话。

坐标换算有个容易忽略的坑:event->pos()返回的是相对本控件的坐标,如果你的游戏窗口内嵌在另一个更大的布局里,记得用event->pos()而不是event->globalPos()。后者相对屏幕,要手动减去窗口偏移,多写一行代码就多一处出错的机会。

4.3 计分、游戏状态与结束画面:简单的状态机

不用引什么设计模式,直接用一个枚举管住游戏状态就够了:

enum GameState { Ready, Playing, Paused, Over };

状态切换的时机很明确:点击“开始”按钮从 Ready 到 Playing;点击黑块继续 Playing;点到白块或漏满 3 次切到 Over;Over 界面显示最终分数和一个“再来一局”按钮。整个状态机在GameBoard里用switch维护,每个状态只做自己的事:

void GameBoard::onTick() { switch (m_state) { case Ready: break; case Playing: moveRows(); generateRows(); checkMisses(); update(); break; case Over: m_timer->stop(); break; } }

有人会问“暂停”怎么处理,我通常不单独做暂停逻辑,因为QTimer::stop()一停,整个动画就冻结了,效果和暂停一致,不需要额外状态。唯一的区别是 Over 状态要停掉计时并且不再响应鼠标,而 Paused 可能还要响应“继续”按钮,所以用枚举区分是有必要的。

5. 避坑指南:Linux 下 QT C++ 小游戏最常见的 4 个翻车现场

5.1 启动即报错:qt.qpa.plugin: could not find the qt platform plugin "linuxfb"

现象:在纯命令行环境或者精简版 Linux 上执行./whiteblock,终端直接输出qt.qpa.plugin: Could not find the Qt platform plugin "linuxfb" in "",进程退出。

原因:QT 启动需要指定“平台插件”,在桌面 Linux 上一般是 xcb 或 wayland,在嵌入式环境里是 linuxfb(Linux Framebuffer)。你的系统里没装对应的插件包,或者 QT 的插件搜索路径不对。

解决:先确认你是在桌面环境里跑,桌面环境下设置export QT_QPA_PLATFORM=xcb再启动。如果你确实要无桌面跑,需要安装 framebuffer 插件:sudo apt install libqt5gui5,然后运行前export QT_QPA_PLATFORM=linuxfb。排查时优先用QT_DEBUG_PLUGINS=1 ./whiteblock看详细日志,它会打印出 QT 扫描了哪些路径、哪些插件加载失败、失败原因是什么。

5.2 编译期报错:cannot mix incompatible Qt library (version ex50601) with this library

现象:编译链接顺利通过,运行到QApplication构造时崩溃,报错内容是cannot mix incompatible Qt library (version ex50601) with this library

原因:系统里同时存在两套 Qt 库,比如/usr/lib/x86_64-linux-gnu/libQt5Core.so.5是一个版本,你从官网装的 Qt 在/opt/Qt/5.15.2/gcc_64/lib是另一个版本,程序加载时混用了。通常是.pro文件里手动加了INCLUDEPATHLIBS指向了非系统路径导致的。

解决:卸载手动安装的套件,或者检查qmake指向的是不是同一个版本:

which qmake qmake --version ldd ./whiteblock | grep Qt5

ldd输出里如果出现两条不同路径的 Qt5 库,就能确认是混用了。删掉.pro里多余的LIBSINCLUDEPATH,统一走QT += core gui widgets声明式依赖,让编译器自己找系统库。

5.3 画面卡顿:QTimer 间隔设置不合理导致的假死

现象:游戏运行几秒钟后,画面明显掉帧,点击响应滞后,CPU 占用接近满核。

原因:我在前面提过,定时器间隔低于 10ms 时,QT 事件循环被频繁打断,paintEvent反复触发,而每帧绘制包含大量fillRect和容器遍历,低配机器扛不住。另一个隐蔽原因是你在paintEvent里做了 new 操作,比如动态创建 QColor 或 QBrush,导致频繁分配内存。

解决:定时器间隔保持在 16ms 以上;把 QColor 声明成 static 常量,避免重复构造:

static const QColor CLICKED_COLOR(80, 80, 80);

检查是否有泄漏,可以用top命令看内存占用是否持续上涨。如果还卡,就把MOVE_SPEED降下来,用“加大步长 + 减少刷新频率”的组合代替“小步长高频刷新”。

5.4 中文乱码或方块:Linux 字体缺失与编码问题

现象:窗口标题、按钮文字、“分数:100”里的中文全部显示成方块或问号。

原因:Linux 桌面环境没装中文字体,或者保修用的字体库不在 QT 的字体搜索路径里。QT 默认从系统字体配置读取,找不到对应字形就退化成豆腐块。

解决:先装字体:

sudo apt install fonts-wqy-microhei

然后在代码里指定字体:

QFont font("WenQuanYi Micro Hei", 14); QApplication::setFont(font);

这行要放在窗口创建之前。注意,不同发行版字体的字族名可能不同,查看系统已有字体可以用fc-list :lang=zh,确认字族名后写进代码,别硬编码“宋体”。

6. 最后一章:发布可执行文件与两个值得做的进阶改造

游戏跑通之后,下一个问题是怎么把它带给别人玩。Linux 上没有 Windows 那种绿色 exe 概念,但可以打一个“免安装运行包”。最简单的方式是静态编译加动态库拷贝:先编译一遍,再用ldd找出所有 .so 依赖,把它们拷到同一个目录,写一个启动脚本。更省事的做法是用linuxdeployqt自动打包,但会多引入一个工具链。我通常先用ldd手动排查一次,因为能看到哪些库是你系统特有的,哪些是通用的,这对后续部署到别的发行版有帮助:

ldd ./whiteblock cp /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5 ./release/ cp /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5 ./release/ cp /usr/lib/x86_64-linux-gnu/libQt5Core.so.5 ./release/

静态编译需要系统安装对应模块的静态库,配置上要加-static,我很少在小游戏上做全静态,因为体积会从 1MB 膨胀到 30MB 以上,收益不大。

进阶改造我建议优先做两件事,性价比最高:一是连击加分和速度自适应,每 10 分加速一次,加一个 Combo 字符串显示在顶部,视觉效果非常明显;二是音效,用QSoundEffect播放一个极短的 click 音,提升操作反馈感。这两个点都不需要重构现有结构,各自几十行代码搞定。我在给同事演示这个项目时,加完音效和速度变化后,游戏的“手感”完全不是一回事,这也是我留下的一条习惯——先做完整能玩的版本,再加反馈层,不要一开始就追求炫酷。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 18:54:53

3个维度讲透车险出险查询接口最佳实践

3个维度讲透车险出险查询接口最佳实践 官方文档太长抓不住重点?别慌,车险出险查询的核心逻辑其实就三块:数据脱敏、接口鉴权、状态同步。很多新人一上来就钻牛角尖,盯着几百页的保信平台对接手册看,结果连最基础的字段映射都没搞懂。今天咱们不讲虚的,直接拆解 最佳实践 里的坑,帮你在面试或实战中快速上手。…

作者头像 李华
网站建设 2026/9/23 18:54:49

3分钟搞懂温水煮青蛙图片原理附完整示例避坑指南

3分钟搞懂温水煮青蛙图片原理附完整示例避坑指南 复制来的代码跑不通不知道怎么调?别急,这锅不全是你的。很多刚入行的嵌入式小白,从网上扒下一段处理“温水煮青蛙”效应的图像算法代码,往自己环境一扔,报错满天飞。其实问题出在环境依赖和参数配置上。今天这篇 完整示例…

作者头像 李华
网站建设 2026/9/23 18:54:43

CentOS卸载软件避坑指南:3个坑让系统秒崩,源码级拆解

CentOS卸载软件避坑指南:3个坑让系统秒崩,源码级拆解 刚入行运维或后端开发,面试时被问到“Linux包管理原理”,很多人答不上来。更扎心的是,你在生产环境CentOS上随手敲个 yum remove 或 rpm -e ,结果服务全挂,业务中断。别慌,这不是你的错,是你没看透底层逻辑。这篇…

作者头像 李华
网站建设 2026/9/23 18:54:41

2026最新Spanglish编程避坑指南:告别环境配置噩梦

2026最新Spanglish编程避坑指南:告别环境配置噩梦 配置环境就卡半天,是不是让你想直接卸载开发工具?很多前端新人一碰到 Spanglish 相关的混合语言环境或特定方言库,第一步就倒在“依赖装不上”或“编码乱码”上。别急,这不是你的问题,是 2026…

作者头像 李华
网站建设 2026/9/23 18:54:17

电脑怎么备份保姆级教程:版本升级后API全变了?

电脑怎么备份保姆级教程:版本升级后API全变了? 版本升级后 API 全变了,备份脚本直接报错,数据丢了一半。别慌,这篇 保姆级教程 带你从零搭建一个健壮的备份系统,彻底解决痛点。 项目目标 很多开发者在接手旧项目时,最头疼的就是备份逻辑。旧代码依赖的 API…

作者头像 李华
网站建设 2026/9/23 18:53:57

微信加好友发送失败避坑指南:3个核心参数救活你的自动化脚本

微信加好友发送失败避坑指南:3个核心参数救活你的自动化脚本 版本升级后 API 全变了,昨天还跑通的代码今天直接报错,这种崩溃感谁懂? 很多做移动端自动化或后端接口的朋友,一遇到 微信加好友发送失败 就抓瞎,其实 90% 的问题都出在参数配置和频率控制上。 这份 避坑指南…

作者头像 李华