使用 Qt Quick 寫了個小遊戲:瘋狂算數。支援 Windows 和 Android 兩個平台。
遊戲簡單,但牽涉到下面你的 Qt Quick 主題:
- 自己實作一個按鈕
- 自适應分辨率
- 國際化
- QML與C++混合程式設計
- APK圖示設定
- APK名稱漢化
- 動畫
其實所有這些内容,在我的書《Qt Quick核心程式設計》裡都講到了,感興趣的朋友可以看我的書。
大概來看一下吧,先看效果。
Android 手機運作效果
下面是 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裡的粒子系統