Lv.的博客

Qt Quick实现的疯狂算数游戏

 

    使用 Qt Quick 写了个小游戏:疯狂算数。支持 Windows 和 Android 两个平台。

    游戏简单,但牵涉到下面你的 Qt Quick 主题:

 

  • 自己实现一个按钮
  • 自适应分辨率
  • 国际化
  • QML与C++混合编程
  • APK图标设置
  • APK名称汉化
  • 动画

 

    其实所有这些内容,在我的书《Qt Quick核心编程》里都讲到了,感兴趣的朋友可以看我的书。

    大概来看一下吧,先看效果。

Android 手机运行效果

    下面是 Android 应用列表:

 

    看到“疯狂算数”那个应用了吧,图标是我自己画的,名字是中文的。

    再来看游戏进行中的效果:

 

    界面中间,第一行是倒计时,数秒的。第二行是算术题。第三行是两个按钮,选择对错;判断正确的话,继续下一题,如果选错了,游戏就结束了,可以看到下面的图。

 

    游戏结束时显示当前答对的题数、历史最好成绩。界面下方是两个按钮,点“再来”可以重玩,点“退出”就结束整个游戏。游戏结束的界面,使用了弹簧动画(SprintgAnimation),有一些动画效果。

源码分析

    源码我们走马观花,摘重要的讲一下。

国际化

    这个简单的示例里,只有 qml 文档中有需要翻译的字符串。在 pro 文件里有一些改动:

 

[javascript] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. TRANSLATIONS = madmath_zh_cn.ts  
  2.   
  3. lupdate_only {  
  4.     SOURCES = main.qml  
  5. }  

    使用 Qt 的命令行开发环境,切换到项目目录,执行 lupdate MadMath.pro 即可生成 ts 文件,然后使用 Linguist 翻译、发布,再把 qm 文件添加到 qrc 里,最后在 main.cpp 中根据用户语言环境加载 qm 文件。

 

    main.cpp 代码如下:

 

[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. #include <QGuiApplication>  
  2. #include <QQmlApplicationEngine>  
  3. #include <QFont>  
  4. #include <QQmlContext>  
  5. #include <QIcon>  
  6. #include <QLocale>  
  7. #include <QTranslator>  
  8. #include "sizeUtil.h"  
  9. #include "problem.h"  
  10.   
  11. int main(int argc, char *argv[])  
  12. {  
  13.     QGuiApplication app(argc, argv);  
  14.     QFont f = app.font();  
  15.     f.setPointSize(24);  
  16.     app.setWindowIcon(QIcon(":/res/madmath_36.png"));  
  17.   
  18.     QLocale locale = QLocale::system();  
  19.     if(locale.language() == QLocale::Chinese)  
  20.     {  
  21.         QTranslator *translator = new QTranslator(&app);  
  22.         if(translator->load(":/madmath_zh_cn.qm"))  
  23.         {  
  24.             app.installTranslator(translator);  
  25.         }  
  26.     }  
  27.   
  28.     QQmlApplicationEngine engine;  
  29.     engine.rootContext()->setContextProperty("sizeUtil", new SizeUtil);  
  30.     engine.rootContext()->setContextProperty("problems", new MathProblem);  
  31.     engine.load(QUrl(QStringLiteral("qrc:///main.qml")));  
  32.   
  33.     return app.exec();  
  34. }  

ImageButton

 

    实现了一个简单的图片按钮—— ImageButton ,在 ImageButton.qml 文件内。所有源码:

 

[javascript] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. import QtQuick 2.0  
  2.   
  3. Rectangle {  
  4.     id: btn;  
  5.     property alias normalImage: normal.source;  
  6.     property alias pressedImage: pressed.source;  
  7.     signal clicked();  
  8.   
  9.     Image {  
  10.         id: normal;  
  11.         anchors.fill: parent;  
  12.     }  
  13.   
  14.     Image {  
  15.         id: pressed;  
  16.         anchors.fill: parent;  
  17.         visible: false;  
  18.     }  
  19.   
  20.     implicitWidth: 64;  
  21.     implicitHeight: 48;  
  22.   
  23.     MouseArea {  
  24.         anchors.fill: parent;  
  25.         onPressed: {  
  26.             pressed.visible = true;  
  27.             normal.visible = false;  
  28.         }  
  29.   
  30.         onReleased: {  
  31.             pressed.visible = false;  
  32.             normal.visible = true;  
  33.             btn.clicked();  
  34.         }  
  35.     }  
  36. }  

    ImageButton 有两个状态:正常和按下。两个状态各自有一个图片。鼠标事件里切换了两个图片。

 

    还定义了一个 clicked() 信号。暴露了属性别名 normalImage 和 pressedImage 用来设置按钮需要的图片。

    ImageButton 用起来也很简单,下面是 main.qml 中的使用示例:

 

[javascript] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. ImageButton {  
  2.     id: wrong;  
  3.     anchors.right: parent.horizontalCenter;  
  4.     anchors.rightMargin: 12;  
  5.     anchors.top: problem.bottom;  
  6.     anchors.topMargin: 20;  
  7.     normalImage: Qt.resolvedUrl("res/wrong_normal.png");  
  8.     pressedImage: Qt.resolvedUrl("res/wrong_selected.png");  
  9.     width: root.dpiFactor * 64;  
  10.     height: root.dpiFactor * 48;  
  11.     onClicked: root.check(false);  
  12. }  

QML与C++混合编程

 

    算术题目的生成和结果判断,我放在了 C++ 中,在 MathProblem 里实现。另外还有 DPI 的一些信息,也在 C++ 中,在 SizeUtil 中实现。

    有关 QML 与 C++ 混合编程的细节,请看我的博客或者我的书——《Qt Quick核心编程》,这里就不再细说了。我们只看一下题目是如何出的,部分源码:

 

[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. QString MathProblem::next()  
  2. {  
  3.     ++m_index;  
  4.     if(m_index == sizeof(g_answers)/sizeof(g_answers[0]))  
  5.     {  
  6.         m_index = 0;  
  7.     }  
  8.   
  9.     int var = qrand() % 2;  
  10.     if(var && (qrand() % 2)) var = -var;  
  11.     m_currentAnswer = g_answers[m_index] + qrand() % 2;  
  12.     m_currentRight = (g_answers[m_index] == m_currentAnswer);  
  13.   
  14.     return QString("%1%2").arg(g_problems[m_index]).arg(m_currentAnswer);  
  15. }  
  16.   
  17. bool MathProblem::test(bool right)  
  18. {  
  19.     return right == m_currentRight;  
  20. }  

    next() 方法生成一道算术题。 MathProblem 维护了一个索引,指向全局的问题数组和答案数组。 next() 递增 m_index ,答案用随机数混淆一下,然后判断混淆后的结果是否与正确答案一致。题目的结果保留在 m_currentRight 这个布尔变量里。

 

    test() 用来测试用户的选择与实际结果是否一致。

    题目在全局数组 g_problems 中,答案在全局数组 g_answers 中。

动画

    当用户答错题时,会从应用顶部弹出一个提示界面。我使用了 SpringAnimation 为这个界面加入了一些动画效果。

    gameOverUI 的代码如下:

 

[javascript] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. Rectangle {  
  2.     id: gameOverUI;  
  3.     border.width: 2;  
  4.     border.color: "white";  
  5.     color: "lightsteelblue";  
  6.     width: root.width * 0.75;  
  7.     height: root.height * 0.75;  
  8.     x: root.width * 0.125;  
  9.     y: -height-1;  
  10.     visible: false;  
  11.   
  12.     Text {  
  13.         id: overTitle;  
  14.         anchors.top: parent.top;  
  15.         anchors.topMargin: sizeUtil.defaultFontHeight();  
  16.         anchors.horizontalCenter: parent.horizontalCenter;  
  17.         font.pointSize: 30;  
  18.         text: qsTr("Game Over");  
  19.         color: "red";  
  20.     }  
  21.   
  22.     Text {  
  23.         anchors.bottom: parent.verticalCenter;  
  24.         anchors.bottomMargin: 10;  
  25.         anchors.right: parent.horizontalCenter;  
  26.         anchors.rightMargin: 8;  
  27.         text: qsTr("New:");  
  28.         horizontalAlignment: Text.AlignRight;  
  29.         color: "black";  
  30.     }  
  31.   
  32.     Text {  
  33.         id: current;  
  34.         anchors.bottom: parent.verticalCenter;  
  35.         anchors.bottomMargin: 10;  
  36.         anchors.left: parent.horizontalCenter;  
  37.         anchors.leftMargin: 8;  
  38.         horizontalAlignment: Text.AlignLeft;  
  39.         color: "blue";  
  40.         font.bold: true;  
  41.     }  
  42.   
  43.     Text {  
  44.         anchors.top: current.bottom;  
  45.         anchors.topMargin: 20;  
  46.         anchors.right: parent.horizontalCenter;  
  47.         anchors.rightMargin: 8;  
  48.         text: qsTr("Best:");  
  49.         horizontalAlignment: Text.AlignRight;  
  50.         color: "black";  
  51.     }  
  52.     Text {  
  53.         id: best;  
  54.         anchors.top: current.bottom;  
  55.         anchors.topMargin: 20;  
  56.         anchors.left: parent.horizontalCenter;  
  57.         anchors.leftMargin: 8;  
  58.         horizontalAlignment: Text.AlignLeft;  
  59.         color: "blue";  
  60.         font.bold: true;  
  61.     }  
  62.   
  63.     Button {  
  64.         anchors.bottom: parent.bottom;  
  65.         anchors.bottomMargin: 40;  
  66.         anchors.right: parent.horizontalCenter;  
  67.         anchors.rightMargin: 16;  
  68.         text: qsTr("Restart");  
  69.         onClicked: {  
  70.             gameOverUI.dismiss();  
  71.             root.start();  
  72.         }  
  73.     }  
  74.   
  75.     Button {  
  76.         anchors.bottom: parent.bottom;  
  77.         anchors.bottomMargin: 40;  
  78.         anchors.left: parent.horizontalCenter;  
  79.         anchors.leftMargin: 16;  
  80.         text: qsTr("Quit");  
  81.         onClicked: Qt.quit();  
  82.     }  
  83.   
  84.     SpringAnimation {  
  85.         id: overAnimation;  
  86.         target: gameOverUI;  
  87.         from: -height - 1;  
  88.         to: root.height * 0.125;  
  89.         spring: 2;  
  90.         damping: 0.2;  
  91.         duration: 1000;  
  92.         property: "y";  
  93.   
  94.         onStarted: {  
  95.             gameOverUI.visible = true;  
  96.         }  
  97.     }  
  98.   
  99.     function dismiss() {  
  100.         y = -height - 1;  
  101.         visible = false;  
  102.     }  
  103.     function fire(currentRecord, bestRecord) {  
  104.         current.text = currentRecord;  
  105.         best.text = bestRecord;  
  106.         overAnimation.start();  
  107.     }  
  108. }  

    SpringAnimation 的 target 属性指向 gameOverUI ,操作 gameOverUI 的 y 属性。一开始 gameOverUI 是不可见的,当动作开始时设置为可见(在 onStarted 信号处理中)。

 

    fire() 方法会在游戏进行中被调用,它启动动画,讲游戏结果赋值给提示界面里的 Text 元素。

    SpringAnimation 的具体用法,参考 Qt 帮助,或者《Qt Quick核心编程》一书,它对 Qt Quick 里的动画类库作了非常详尽的介绍。

APK 设置

    给 Android 版本创建一个 AndroidManifest.xml ,在项目视图里双击就可以打开图形化编辑界面,可以选择你设计的图标。参考《Qt on Android核心编程》一书,或者“Qt on Android:图文详解Hello World全过程”。

自适应屏幕分辨率

    我在“Qt on Android:创建可伸缩界面”一文中讲了 Qt on Android 如何适应 Android 手机多变的分辨率。这里就不再细说了。只看一下 QML 里如何根据 DPI  来设置图片按钮的大小,代码:

 

[javascript] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. ImageButton {  
  2.     id: right;  
  3.     anchors.left: parent.horizontalCenter;  
  4.     anchors.leftMargin: 12;  
  5.     anchors.top: problem.bottom;  
  6.     anchors.topMargin: 20;  
  7.     normalImage: Qt.resolvedUrl("res/right_normal.png");  
  8.     pressedImage: Qt.resolvedUrl("res/right_selected.png");  
  9.     width: root.dpiFactor * 64;  
  10.     height: root.dpiFactor * 48;  
  11.     onClicked: root.check(true);  
  12. }  

    如你所见,我设置 right 按钮的 width 为 root.dpiFactor * 64 。 64 是图片的宽度,单位是像素。 dpiFactor 来自 SizeUtil :

 

 

[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. qreal SizeUtil::dpiFactor()  
  2. {  
  3.     QScreen *screen = qApp->primaryScreen();  
  4.     return screen->logicalDotsPerInch() / 72;  
  5. }  

    

    好啦,到此结束了。完整的项目代码下载:点击下载

 

--------

 

回顾一下我的Qt Quick系列文章:

 

posted @   Avatarx  阅读(549)  评论(0编辑  收藏  举报
(评论功能已被禁用)
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示