尧图精选

用Qt实现Flappy Bird全流程:环境配置、物理循环与打包避坑

🕒 发布时间:2026/10/1 22:24:26 📁 来源:尧图网络
简介由Qt框架编写的Flappy Bird克隆游戏完整源码包还原了经典弹跳小鸟的玩法与界面适合初步接触Qt游戏开发或希望巩固C面向对象实践的开发者也可作为课程设计、业余练手项目的参考。压缩包共57个文件其中cpp/h源码负责游戏逻辑与窗口实现png图片素材用于角色、背景和计分板mp3音频提供撞击、得分等音效另有pro/qrc工程配置整体容量仅490KB目录按源码、图片、声音划分便于按模块阅读。已有189人学习下载。项目围绕Qt核心机制展开重点演示了QGraphicsView/QGraphicsScene构建2D场景、信号槽处理点击与碰撞、QTimer驱动连续动画并涵盖资源文件加载、跨平台编译等细节。研读这份代码可以完整看到Flappy Bird的循环逻辑——从鸟的上升下降、管道生成移动到碰撞判定与分数刷新对理解Qt游戏架构和游戏循环设计很有帮助对后续独立开发Qt项目大有裨益。1. 用 Qt 制作 Flappy Bird从压缩包到能玩的完整落地路径下载一个 QtFlappyBird-master.zip 回来练手是很多 Qt 学习者第一次真正把界面、事件循环、碰撞和动画捏在一起的动作。这类压缩包通常是一个用 Qt Widgets 写的精简 Flappy Bird看起来没几个源文件真要跑起来却要跨过环境、资源、打包三座山。Qt 游戏的坑不在玩法有多难而在编译过后能不能稳定运行、换台机器还能不能双击打开。这篇从一个压缩包讲到可执行程序覆盖选型、玩法拆解、物理循环、碰撞判定以及发布排错适合刚学完 Qt 基础、想拿小游戏做一次纵深练习的人。2. 环境与工程结构让 QtFlappyBird 的 .pro 项目先编译过2.1 Qt 5.15.2 还是 Qt 6安装时先做对的三个选择拿到这类压缩包第一件事不是读代码而是确认本机 Qt 环境能不能把这个 .pro 工程接住。常见做法是先装 Qt 5.15.2因为它既是 LTS 版本又是大量 Qt 小游戏教程默认使用的版本网上能搜到的 qt 5.15.2 下载安装包也最齐全。Qt 6 当然也能写 Flappy Bird但如果压缩包里的代码用的是 QGraphicsScene 加 QGraphicsPixmapItem 这套老接口Qt 6 下直接编译大概率要处理模块拆分带来的头文件变化不值得在环境上消耗耐心。安装时有三个选择容易做错。第一个是编译器套件Qt 官方安装包会同时提供 MinGW 和 MSVC 两套工具链下载时必须把对应版本的 MinGW 64-bit 勾上否则之后打开工程会提示没有可用套件。第二个是安装路径避免装到带空格或中文的目录Qt 的命令行工具和 windeployqt 对路径空格很敏感虽然多数时候能跑但排错时你会多花半小时。第三个是 Qt Creator 里构建套件的选择打开工程后要在“工具 → 选项 → Kits”里确认编译器、qmake、CMake 三项都指向同一个 Qt 版本很多编译报错都源自 Kit 里混用了不同版本的 qmake。常见做法是安装时把 Qt 5.15.2 的 MinGW 64-bit、Sources 和 Qt Debug 组件全部选中安装完成后先在欢迎页的“示例”里打开一个自带 Demo 跑一遍确认这套环境本身是通的然后再打开你的游戏工程。这样可以把“环境问题”和“工程问题”隔离后面省很多事。2.2 打开 .pro 前先观察这类 zip 的工程骨架是什么一个命名为 QtFlappyBird-master.zip 的压缩包解压后通常是一套 qmake 工程最少包含一个 .pro 文件、几个 .cpp/.h 文件和一个 .qrc 资源文件有时还带 images 或 sound 目录。先用文本编辑器把 .pro 打开看三行QT 模块、TARGET、SOURCES 路径。这比直接双击工程文件更能判断它的组织方式。一个典型的 Qt 游戏 .pro 长这样QT core gui widgets TARGET QtFlappyBird TEMPLATE app SOURCES \ main.cpp \ gamemanager.cpp \ birditem.cpp \ pipeitem.cpp HEADERS \ gamemanager.h \ birditem.h \ pipeitem.h RESOURCES \ res.qrcQT core gui widgets声明了依赖模块Flappy Bird 这类界面游戏必须带 widgets少了它 QGraphicsScene 相关的类都会报“no such file”。TARGET是最终生成的 exe 名字。SOURCES和HEADERS如果写的是相对路径说明工程结构是源码放在 .pro 同层目录如果写成src/xxx.cpp那你要把解压后的目录结构保持原样不要自己调整文件夹层级。打开工程前还有一个经验整个工程路径不要出现中文也不要放在桌面这样的深层路径下。Qt 的构建系统把中间文件放在 build 目录路径一旦有中文MinGW 的 make 和资源编译器偶尔会给出莫名其妙的路径错误。我一般会把压缩包解压到 D:\QtProjects\QtFlappyBird干净利落。把工程文件和源码目录结构搞清楚后心里就有了一张地图main.cpp 负责创建窗口birditem 和 pipeitem 是两个游戏元素gamemanager 通常是核心逻辑。后面改代码时知道去哪里找比一头扎进 Qt Creator 再逐步点开文件要快得多。2.3 命令行构建与“cannot mix incompatible Qt library”的定位思路在 Qt Creator 里直接点运行是最快的但遇到编译失败时命令行往往能暴露更真实的错误。常见做法是打开 Qt 官方提供的命令行环境开始菜单里的 MinGW 64-bit 终端或者手工把 Qt 的 bin 和工具链目录加进 PATH然后切到工程目录执行qmake QtFlappyBird.pro mingw32-make -j8qmake 会根据 .pro 生成 Makefilemingw32-make 按它编译链接。如果qmake -v打印出来的版本和你在 Qt Creator 里选的不一致问题基本出在 PATH 的 Qt bin 目录冲突。比如你系统里装了多个 Qt命令行终端先找到 Qt 6 的 qmake再去链接 Qt 5 的库就可能出现fatal: cannot mix incompatible Qt library (version ex50601) with this librar这类版本段不匹配的报错。这时先做两件事执行where qmake确认 qmake 实际路径再执行echo %PATH%看有没有混入其它 Qt 版本。处理办法是重新打开干净的 Qt 命令行环境或者把无关的 Qt bin 目录从 PATH 里临时摘掉然后删掉 build 目录重新 qmake。命令行构建的意义不是替代 IDE而是让你在失败时能确认“用哪个工具链、调哪个库”这件事环境问题基本都是这样定位出来的。3. 玩法落地把 Flappy Bird 的物理模型与碰撞检测写进 QGraphicsScene3.1 QGraphicsScene/QGraphicsView 还是 QML项目包为什么选 Widgets 版本Flappy Bird 在 Qt 里有两条常见技术路线一条是 QML 加 JavaScript 逻辑一条是 C 配合 Qt Widgets 的 QGraphicsScene 图形视图框架。压缩包项目大多数走第二条原因是它结构直观场景负责管理所有游戏元素视图负责显示游戏元素继承 QGraphicsPixmapItem鼠标和键盘事件直接由视图分发。QML 路线更适合做复杂动画和界面特效但放到 Flappy Bird 这个规模上反而多了一层 QML 与 C 交互的成本。图形视图框架的另一个好处是碰撞检测开箱即用。QGraphicsItem 自带的 collidesWithItem 能感知图元之间的相交区域你不需要自己写矩形相交逻辑只要控制好每个图元的 boundingRect 和 shape 就能获得稳定的碰撞判断。这对 Flappy Bird 来说尤其省事因为游戏里最主要的判定就是小鸟和上下管道是否碰在一起。我一般会把渲染和逻辑分两层看QGraphicsScene 管对象生命周期QTimer 驱动逻辑刷新paint 由 QGraphicsPixmapItem 自动完成。这样整个项目的复杂度被控制在一个很小的范围即使压缩包里的代码写得比较乱你也能按这条主线把它重构清楚。3.2 用 QTimer 驱动帧最小可复现的物理循环Flappy Bird 的核心手感来自一个非常简单的物理模型每帧给小鸟一个向下的重力加速度点击时给一个向上的初速度位置每帧按速度累加。这个模型在 Qt 里的实现通常是一个 QTimer 按固定间隔触发 tick 函数间隔取 16ms 时接近 60fps 的刷新节奏。下面这段是这类项目里最核心的 BirdItem 实现代码量不大但决定了整个游戏的手感// birditem.h class BirdItem : public QGraphicsPixmapItem { public: explicit BirdItem(QGraphicsItem *parent nullptr); void flap(); // 按键后给一个向上的初速度 void tickFrame(); // 每帧更新重力加速度与位移 qreal vY() const { return m_vy; } private: qreal m_vy; // 当前垂直速度单位 px/frame }; // birditem.cpp BirdItem::BirdItem(QGraphicsItem *parent) : QGraphicsPixmapItem(parent), m_vy(0) { setPixmap(QPixmap(:/images/bird.png)); setPos(100, 220); // 出生位置在场景左侧 100px、纵向 220px 处 } void BirdItem::flap() { m_vy -9.5; // 向上初速度负值表示向上 } void BirdItem::tickFrame() { const qreal kGravity 0.45; // 重力加速度单位 px/frame^2 m_vy kGravity; setPos(x(), y() m_vy); // 按当前帧速度移动 if (y() 0) setPos(x(), 0); // 撞到天花板时钳制防止飞出场景 }逻辑要点在 tickFrame 里先给速度叠加一个固定的重力增量再把速度累加到位置坐标上。这里刻意没有使用真实时间戳因为 QTimer 本身不是精确计时器按帧计算能让行为更可预测也方便调参。flap()里的-9.5和kGravity 0.45是两个最关键的手感参数初速度绝对值决定单次点击的上升幅度重力系数决定下落加速度两者配合决定小鸟的整体轨迹。运行这个物理循环还需要在管理类里启动一个定时器// 在 GameManager 的 startGame() 中 m_timer new QTimer(this); connect(m_timer, QTimer::timeout, this, GameManager::onTick); m_timer-start(16); // 约 60 帧/秒QTimer 的间隔设为 16ms 是常见做法但要注意它只是“尽量接近”60fps实际触发间隔受系统负载影响。如果你要做严格的帧率控制可以在 tick 里用 QElapsedTimer 计算真实 delta再按 delta 缩放位移。对 Flappy Bird 这种轻量游戏16ms 固定间隔已经完全够用而且代码简单得多。3.3 管道生成、移动、删除与碰撞矩形盒参数决定难易小鸟的物理做好后游戏的一大半内容就完成了剩下的是障碍物管道。管道的逻辑可以归纳为四件事定时生成、向左滚动、越界删除、碰撞判定。按固定帧数生成管道是常见做法比如每 90 帧生成一对上下管道管道宽度通常取 70 到 80px上下管道之间的空隙取 160 到 180px。管道的滚动代码通常这样写void PipeItem::scroll() { setPos(x() - 2.6, y()); // 每帧向左移动 2.6px if (x() kPipeWidth 0) emit needDelete(this); // 完全移出场景后通知管理类回收 }这里的2.6是横向滚动速度单位是 px/frame乘以 60 帧就是 156px/s。滚动速度不宜太快否则玩家反应时间被压得过于紧张也不宜太慢否则游戏节奏拖沓。配合管道生成间隔 90 帧两条管道之间的水平距离大约是 234px这个数值要大于管道宽度加上小鸟宽度才能保证玩家有通道可以通过。碰撞判定是 Flappy Bird 最容易做过头的地方。直接使用 QGraphicsPixmapItem 的默认包围盒会算上图片的透明边缘视觉上没撞到、判定上却已经死了这对玩家非常不公平。常见做法是重写 shape() 返回一个内缩的碰撞矩形给玩家留出容差QPainterPath PipeItem::shape() const { QPainterPath path; QRectF rect boundingRect(); // 左右各内缩 6px只用一个矩形近似管道主体 path.addRect(rect.adjusted(6, 0, -6, 0)); return path; }adjusted(6, 0, -6, 0)表示左右两边都向内收缩 6px这样碰撞只发生在管道本体而图片的羽化边缘和透明区域不会误伤玩家。困难模式可以把收缩值调小到 2px简单模式调到 10px 以上。小鸟的 shape() 同理把身体外围一圈透明区域忽略掉游戏体验会明显变好。碰撞触发后要做的动作按顺序来先停止 QTimer再播放一个死亡动画或者直接显示结束界面最后把场景里所有管道清空并重置小鸟位置。不要在图元滚动过程中直接调用删除因为场景遍历时删元素会让迭代器失效正确做法是通过信号通知管理类让管理类统一回收。4. 界面与资源用 Qt Designer 搭 UI用 qrc 管图片用 windeployqt 打包4.1 用 Qt Designer 布置开始与结束界面从 .ui 到代码Flappy Bird 的窗口结构可以拆成三块游戏主场景、分数提示、开始和结束时的按钮。如果你从压缩包工程入手看到的通常是用 Qt Designer 拖出来的 .ui 文件里面放了一个 QGraphicsView 作为游戏画布再叠几个 QLabel 显示分数和状态。qt designer 界面设计的做法是把静态元素用拖拽完成把动态游戏逻辑留给 C 代码。一个标准的顺序是在 Qt Designer 里新建 MainWindow把 centralWidget 设置成 QGraphicsView然后在窗口上放一个 QPushButton 作为“开始”按钮。按钮点击后触发自定义槽函数在槽里创建一个 QGraphicsScene设定场景矩形大小再把小鸟和管道加进去。这个流程的好处是界面布局所见即所得游戏逻辑不需要关心按钮的位置和字体大小。分数显示一般用 QGraphicsTextItem 叠加在场景上或者用一个 QLabel 悬浮在窗口右上角。我习惯用 QGraphicsTextItem因为它的字体和颜色能随场景一起缩放打包时也不用额外注意字体依赖。分数逻辑不要每帧更新而是在小鸟通过一对管道时计数加一这样性能开销为零逻辑也更贴近原版。如果你拿到的压缩包里没有 .ui 文件而是全部用代码创建控件也不影响思路一样创建 QGraphicsView 设为中心部件其余控件作为它的兄弟组件叠加。Qt Designer 更多是提供排版便利对 Flappy Bird 这种界面非常简单的游戏手工写代码反而更直白。4.2 qrc 资源系统与图片路径为什么用相对路径加载总会失败很多人在跑通游戏后遇到一个诡异问题在 Qt Creator 里运行正常单独去 build 目录双击 exe 就黑屏或者不显示图片。原因大多是图片加载用了磁盘相对路径比如QPixmap(images/bird.png)而启动 exe 时当前工作目录不是工程目录Qt 自然找不到图片。正确做法是把图片打包进 Qt 资源系统。在 qrc 文件里声明资源然后在代码里用:/前缀引用RCC qresource prefix/ file aliasimages/bird.pngassets/bird.png/file file aliasimages/pipe_top.pngassets/pipe_top.png/file file aliasimages/pipeline_bottom.pngassets/pipe_bottom.png/file /qresource /RCC这里的 alias 是你在代码里使用的虚拟路径实际文件路径则写在尖括号里。代码里QPixmap(:/images/bird.png)就能加载因为这个资源已经编译进了二进制文件不依赖当前目录也不依赖图片文件是否被手动拷贝到 exe 旁边。qt界面设计阶段如果图片显示空白检查顺序应该是qrc 里有没有加文件、alias 路径和代码里是否一致、修改 qrc 后有没有重新编译。一个容易忽略的坑是 qrc 文件更新后Qt Creator 不一定每次都会重新编译资源。只要 qrc 内容或图片文件发生变化建议先执行 qmake 重新生成 Makefile再构建。否则你改了图片运行看到的还是旧的会白白浪费时间。4.3 用 windeployqt 打包release 版依赖、平台插件和运行目录发布 Qt 程序是这类游戏项目最容易卡住的地方。Debug 版本在开发机上有 Qt Creator 的环境变量撑着没问题但一个干净的 exe 需要一堆 Qt 动态库和插件才能运行。Windows 上的打包工具是 windeployqt它会自动扫描 exe 的依赖把需要的 DLL 和插件复制到目标目录。常见做法是先在 Release 模式下构建工程然后在命令行里执行mkdir -p release_deploy cd build-QtFlappyBird-Desktop_Qt_5_15_2_MinGW_64_bit-Release windeployqt --release --no-translations \ --dir ../release_deploy FlappyBird.exe cp FlappyBird.exe ../release_deploy/windeployqt 会自动生成 platforms、styles、imageformats 等目录其中最关键的是 platforms/qwindows.dll这是 Qt 在 Windows 上创建窗口所必需的平台插件。很多“双击 exe 没反应”的问题根源是发布目录里少了 platforms 文件夹。--no-translations表示不复制 Qt 自带的多语言翻译包能减小发布体积。如果你要在 Linux 或树莓派上发布工具换成 linuxdeployqt思路完全一致。ARM 开发板上需要注意平台插件名通常是 linuxfb 或 eglfs运行时通过-platform linuxfb参数指定。已经习惯了 Qt Creator 的“一键运行”之后接手打包发布时心态要切换成“把程序当作黑盒考一遍”所有依赖只能在运行时暴露。5. 常见问题与避坑闪退 0xc0000005、平台插件与 Qt 版本混用排查5.1 闪退 0xc0000005定时器回调里访问了已删除的场景对象现象游戏运行中点击重启程序直接退出或弹窗提示 0xc0000005 访问越界。这个问题在 Qt 游戏里出现频率很高尤其当你在 restart 逻辑里先清空场景再启动定时器。原因QTimer 触发的槽函数在遍历管道列表时某个管道在图元列表中已经被删除但槽函数仍然持有它的指针访问它的成员函数时触发了野指针。另一种常见场景是场景清理函数把管道 delete 了而定时器还在继续触发下一帧又去访问已释放的内存。解决在清理之前先停止定时器并且删除图元时不要直接在遍历循环里 delete而是记录到待删除列表遍历结束后统一回收。更稳妥的做法是在 GameManager 里设置一个bool m_gameActive标志位tick 函数开头先判断这个标志为 false 就直接 return这样即使定时器没有立即停止回调也不会访问已销毁的对象。5.2 运行提示找不到 Qt 平台插件 platform “windows” / “linuxfb”现象发布后的 exe 在另一台电脑上双击弹窗提示qt.qpa.plugin: could not find the qt platform plugin windows in或者开发板上运行提示找不到 linuxfb 插件。原因发布目录里没有 platforms/qwindows.dll或者QT_QPA_PLATFORM_PLUGIN_PATH环境变量没有指向插件目录。Qt 程序启动时会先加载平台插件找不到就无法创建窗口表现就是闪退或弹错误框。解决回到开发机上重新执行 windeployqt确认输出目录里生成了 platforms 文件夹。如果手动复制依赖需要从 Qt 安装目录的 plugins/platforms 下把 qwindows.dll 复制到发布目录的 platforms 子目录。Linux 上多确认一个-platform linuxfb参数是否正确插件文件名通常是 libqlinuxfb.so缺少它时和 Windows 的报错逻辑一模一样。5.3 版本段不匹配cannot mix incompatible Qt library 的根因现象命令行编译或运行时出现fatal: cannot mix incompatible Qt library (version ex50601) with this librar之类的错误后面跟着一串十六进制版本号。原因Qt 的库在构建时会把自己的版本信息写进动态库链接或加载时如果某个 Qt 版本的头文件生成代码与运行时 DLL 版本段不同就会拒绝继续执行。最常见的情况是 PATH 里混入了两个 Qt 安装目录比如一个 Qt 5.15.2一个 Qt 5.6 或 Qt 6编译器找到一头运行找到另一头。解决先把 PATH 里所有 Qt bin 目录清干净只保留当前在用的 Qt 5.15.2 的 bin 和 MinGW 工具链目录然后重新打开命令行执行qmake -v确认版本号。再删掉 build 目录执行 qmake 加 mingw32-make 重新构建。如果用的是 Qt Creator检查“构建套件”页签里 qmake 路径是否被改过。5.4 中文路径导致资源加载失败或构建错误现象代码在 C 盘工程目录下编译通过换成带中文的路径后图片加载不出来甚至编译中途报找不到文件。原因qmake 生成的 Makefile 对非 ASCII 路径支持不一致尤其是 MinGW 的工具链在解析中文路径时可能丢失编码信息。资源文件如果使用相对路径引用也会因为当前工作目录变化而失效。解决工程根路径、build 目录、资源文件路径一律使用英文字符。图片、音频资源全走 qrc代码里只写:/虚拟路径不写任何磁盘路径。这个习惯养成后不仅能避开中文路径问题还顺手解决了“exe 换目录运行找不到资源”的麻烦。5.5 MinGW 和 MSVC 工具链混用导致链接失败现象用 MinGW 套件打开一个原本按 MSVC 配置的工程链接时报cannot find -lQt5Core或者一堆 undefined reference。原因MinGW 的链接器找的是 Qt5Core.a 这种 MinGW 格式的导入库MSVC 环境下生成的是 Qt5Core.lib。Qt 官方安装包虽然同时提供两套库但它们分别放在不同子目录工具链一旦选错链接器找不到对应格式的导入库就会报错。解决在 Qt Creator 里根据压缩包工程常见的编译方式统一套件。如果工程里带.pro用 MinGW 套件最省事如果带.sln或明显包含msvc字样的配置再用 MSVC 工具链。两个套件不要混合进同一个 build 目录切换套件时新建 build 目录重新构建这是 Qt 项目里几乎不会出错的铁律。6. 验证与调参把 FlappyBird 的手感调到你想要的值代码能跑起来只是第一步Flappy Bird 这类游戏真正花时间的是调参数。我常用的方法是把“手感验证”做成一套固定流程先确认帧率稳定再看跳跃轨迹是否符合直觉最后检查碰撞边界是否和视觉匹配。帧率验证用 QElapsedTimer 最直接。在 tick 函数开头取一次时间戳末尾再取一次累计若干帧后求平均打印每帧耗时。如果平均耗时超过 18ms画面就会明显发肉需要检查是不是场景里图元过多或者有没有在渲染循环里做了密集的字符串拼接。Flappy Bird 的图元数量很少正常情况每帧耗时应该稳定在 2ms 以内剩余时间都是系统空闲。跳跃轨迹的调参顺序建议固定下来先把重力 kGravity 定在 0.4 到 0.5 之间再调 flap 的初速度。初速度绝对值越大单次点击的跳跃越高重力越大下落越急。两个参数共同决定一次完整跳跃的高度你可以让小鸟跳一次观察它上升的最高点是否在管道空隙的中间偏上位置。之后再去调管道垂直空隙和水平滚动速度。先定运动曲线再定障碍物布局这样每次只动一个变量不会越调越乱。碰撞边界的验证有一个非常实用的小技巧在 BirdItem 和 PipeItem 的 paint 函数里临时画一个半透明的红色矩形显示 shape() 返回的实际碰撞区域。然后让小鸟从管道边缘缓缓滑过看视觉接触和碰撞触发是否一致。如果玩家觉得“明明没碰到就死了”就把 shape() 的收缩值调大反过来如果觉得太假就调小。验证完成后记得删掉调试用的绘制代码否则会让人误以为游戏本身有红色框。我现在拿到一个 Qt 小游戏工程第一件事还是先看 .pro 里的 QT 模块行然后确认 Kit 和 PATH最后才打开代码。这个习惯让我少踩了不知道多少编译和闪退的坑。希望这一套从环境到玩法再到发布的完整路径能帮你把 QtFlappyBird 真正做成一个拿得出手的练手项目。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →