Qt Quick实现的疯狂算数游戏

               

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

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

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

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

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

Android 手机运行效果

    下面是 Android 应用列表:



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

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



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



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

源码分析

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

国际化

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

TRANSLATIONS = madmath_zh_cn.tslupdate_only {    SOURCES = main.qml}

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

    main.cpp 代码如下:

#include <QGuiApplication>#include <QQmlApplicationEngine>#include <QFont>#include <QQmlContext>#include <QIcon>#include <QLocale>#include <QTranslator>#include "sizeUtil.h"#include "problem.h"int main(int argc, char *argv[]){    QGuiApplication app(argc, argv);    QFont f = app.font();    f.setPointSize(24);    app.setWindowIcon(QIcon(":/res/madmath_36.png"));    QLocale locale = QLocale::system();    if(locale.language() == QLocale::Chinese)    {        QTranslator *translator = new QTranslator(&app);        if(translator->load(":/madmath_zh_cn.qm"))        {            app.installTranslator(translator);        }    }    QQmlApplicationEngine engine;    engine.rootContext()->setContextProperty("sizeUtil", new SizeUtil);    engine.rootContext()->setContextProperty("problems", new MathProblem);    engine.load(QUrl(QStringLiteral("qrc:///main.qml")));    return app.exec();}

ImageButton

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

import QtQuick 2.0Rectangle {    id: btn;    property alias normalImage: normal.source;    property alias pressedImage: pressed.source;    signal clicked();    Image {        id: normal;        anchors.fill: parent;    }    Image {        id: pressed;        anchors.fill: parent;        visible: false;    }    implicitWidth: 64;    implicitHeight: 48;    MouseArea {        anchors.fill: parent;        onPressed: {            pressed.visible = true;            normal.visible = false;        }        onReleased: {            pressed.visible = false;            normal.visible = true;            btn.clicked();        }    }}

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

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

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

    ImageButton {        id: wrong;        anchors.right: parent.horizontalCenter;        anchors.rightMargin: 12;        anchors.top: problem.bottom;        anchors.topMargin: 20;        normalImage: Qt.resolvedUrl("res/wrong_normal.png");        pressedImage: Qt.resolvedUrl("res/wrong_selected.png");        width: root.dpiFactor * 64;        height: root.dpiFactor * 48;        onClicked: root.check(false);    }

QML与C++混合编程

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

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

QString MathProblem::next(){    ++m_index;    if(m_index == sizeof(g_answers)/sizeof(g_answers[0]))    {        m_index = 0;    }    int var = qrand() % 2;    if(var && (qrand() % 2)) var = -var;    m_currentAnswer = g_answers[m_index] + qrand() % 2;    m_currentRight = (g_answers[m_index] == m_currentAnswer);    return QString("%1%2").arg(g_problems[m_index]).arg(m_currentAnswer);}bool MathProblem::test(bool right){    return right == m_currentRight;}

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

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

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

动画

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

    gameOverUI 的代码如下:

    Rectangle {        id: gameOverUI;        border.width: 2;        border.color: "white";        color: "lightsteelblue";        width: root.width * 0.75;        height: root.height * 0.75;        x: root.width * 0.125;        y: -height-1;        visible: false;        Text {            id: overTitle;            anchors.top: parent.top;            anchors.topMargin: sizeUtil.defaultFontHeight();            anchors.horizontalCenter: parent.horizontalCenter;            font.pointSize: 30;            text: qsTr("Game Over");            color: "red";        }        Text {            anchors.bottom: parent.verticalCenter;            anchors.bottomMargin: 10;            anchors.right: parent.horizontalCenter;            anchors.rightMargin: 8;            text: qsTr("New:");            horizontalAlignment: Text.AlignRight;            color: "black";        }        Text {            id: current;            anchors.bottom: parent.verticalCenter;            anchors.bottomMargin: 10;            anchors.left: parent.horizontalCenter;            anchors.leftMargin: 8;            horizontalAlignment: Text.AlignLeft;            color: "blue";            font.bold: true;        }        Text {            anchors.top: current.bottom;            anchors.topMargin: 20;            anchors.right: parent.horizontalCenter;            anchors.rightMargin: 8;            text: qsTr("Best:");            horizontalAlignment: Text.AlignRight;            color: "black";        }        Text {            id: best;            anchors.top: current.bottom;            anchors.topMargin: 20;            anchors.left: parent.horizontalCenter;            anchors.leftMargin: 8;            horizontalAlignment: Text.AlignLeft;            color: "blue";            font.bold: true;        }        Button {            anchors.bottom: parent.bottom;            anchors.bottomMargin: 40;            anchors.right: parent.horizontalCenter;            anchors.rightMargin: 16;            text: qsTr("Restart");            onClicked: {                gameOverUI.dismiss();                root.start();            }        }        Button {            anchors.bottom: parent.bottom;            anchors.bottomMargin: 40;            anchors.left: parent.horizontalCenter;            anchors.leftMargin: 16;            text: qsTr("Quit");            onClicked: Qt.quit();        }        SpringAnimation {            id: overAnimation;            target: gameOverUI;            from: -height - 1;            to: root.height * 0.125;            spring: 2;            damping: 0.2;            duration: 1000;            property: "y";            onStarted: {                gameOverUI.visible = true;            }        }        function dismiss() {            y = -height - 1;            visible = false;        }        function fire(currentRecord, bestRecord) {            current.text = currentRecord;            best.text = bestRecord;            overAnimation.start();        }    }

    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  来设置图片按钮的大小,代码:

    ImageButton {        id: right;        anchors.left: parent.horizontalCenter;        anchors.leftMargin: 12;        anchors.top: problem.bottom;        anchors.topMargin: 20;        normalImage: Qt.resolvedUrl("res/right_normal.png");        pressedImage: Qt.resolvedUrl("res/right_selected.png");        width: root.dpiFactor * 64;        height: root.dpiFactor * 48;        onClicked: root.check(true);    }

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

qreal SizeUtil::dpiFactor(){    QScreen *screen = qApp->primaryScreen();    return screen->logicalDotsPerInch() / 72;}

    

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


--------

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

           

再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值