天天看點

Qt Quick實作的瘋狂算數遊戲Android 手機運作效果源碼分析

    使用 Qt Quick 寫了個小遊戲:瘋狂算數。支援 Windows 和 Android 兩個平台。

    遊戲簡單,但牽涉到下面你的 Qt Quick 主題:

  • 自己實作一個按鈕
  • 自适應分辨率
  • 國際化
  • QML與C++混合程式設計
  • APK圖示設定
  • APK名稱漢化
  • 動畫

    其實所有這些内容,在我的書《Qt Quick核心程式設計》裡都講到了,感興趣的朋友可以看我的書。

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

Android 手機運作效果

    下面是 Android 應用清單:

Qt Quick實作的瘋狂算數遊戲Android 手機運作效果源碼分析

    看到“瘋狂算數”那個應用了吧,圖示是我自己畫的,名字是中文的。

    再來看遊戲進行中的效果:

Qt Quick實作的瘋狂算數遊戲Android 手機運作效果源碼分析

    界面中間,第一行是倒計時,數秒的。第二行是算術題。第三行是兩個按鈕,選擇對錯;判斷正确的話,繼續下一題,如果選錯了,遊戲就結束了,可以看到下面的圖。

Qt Quick實作的瘋狂算數遊戲Android 手機運作效果源碼分析

    遊戲結束時顯示目前答對的題數、曆史最好成績。界面下方是兩個按鈕,點“再來”可以重玩,點“退出”就結束整個遊戲。遊戲結束的界面,使用了彈簧動畫(SprintgAnimation),有一些動畫效果。

源碼分析

    源碼我們走馬觀花,摘重要的講一下。

國際化

    這個簡單的示例裡,隻有 qml 文檔中有需要翻譯的字元串。在 pro 檔案裡有一些改動:

TRANSLATIONS = madmath_zh_cn.ts

lupdate_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.0

Rectangle {
    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系列文章:

  • Qt Quick 簡介
  • QML 語言基礎
  • Qt Quick 之 Hello World 圖文詳解
  • Qt Quick 簡單教程
  • Qt Quick 事件處理之信号與槽
  • Qt Quick事件處理之滑鼠、鍵盤、定時器
  • Qt Quick 事件處理之捏拉縮放與旋轉
  • Qt Quick 元件與對象動态建立詳解
  • Qt Quick 布局介紹
  • Qt Quick 之 QML 與 C++ 混合程式設計詳解
  • Qt Quick 圖像處理執行個體之美圖秀秀(附源碼下載下傳)
  • Qt Quick 之 PathView 詳解
  • Qt Quick執行個體之挖頭像
  • Qt Quick綜合執行個體之檔案檢視器
  • Qt Quick調試之顯示代碼行号
  • Qt Quick實作的塗鴉程式
  • Qt Quick播放GIF動畫
  • Qt Quick 中的 drag and drop(拖放)
  • Qt Quick裡的AnimatedSprite的用法
  • Qt Quick裡的粒子系統

繼續閱讀