qml demo分析(maroon-小游戏)

1、效果展示

  这篇文章我还是分析一个qt源码中的qml程序,程序运行效果如下图所示。

 图1  游戏开始

图2  游戏中

2、源码分析

  这个游戏的源码文件比较多,为了能更清楚的了解整个代码,我先整体分析代码,然后再局部分析。

1、源码目录结构

图3  源码目录

  如图3所示,是小游戏的源码目录,下边我分别按文件名称来介绍该文件的功能

  • TowerBase.qml:模型父类,定义了一些共有的属性,比如血量,攻击距离和攻击伤害等
  • Bomb.qml:海藻,父类为TowerBase.qml
  • Factory.qml:星星,父类为TowerBase.qml
  • Ranged.qml:章鱼,父类为TowerBase.qml
  • Melee.qml:螃蟹,父类为TowerBase.qml
  • BuildButton.qml:左键菜单中的一项
  • GameCanvas.qml:游戏中画布
  • GameOverScreen.qml:游戏结束画布
  • InfoBar.qml:游戏信息,包含当前血量显示,当前救下的鱼和金币数量
  • maroon.qml:程序主文件
  • MobBase.qml:带有鱼的气泡
  • NewGameScreen.qml:程序启动时画布,和GameCanvas、GameOverScreen组成了一张竖直的大画布
  • SoundEffect.qml:声音文件,可以播放音频文件
  • logic.js:js文件,完成一些具体的逻辑操作,比如新游戏清空内存,游戏推进更新内存等。

2、交互分析

  该游戏为一个塔防类游戏,类似于植物大战僵尸,但是游戏丰富程度和游戏流程度却要差上很多,不过既然是demo,我们就只学习它的方式方法。首先启动游戏,游戏界面使用NewGameScreen组件展示ui,启动页只包含游戏标题、带气泡的鱼和开始按钮,在动态图1的第一帧就可以看到,点击开始游戏,整个界面下滑,然后出现倒数3秒,并开始游戏,开始游戏画面用GameCanvas组件展示,但不包括游戏当前血量和金币数值等信息。随着游戏的推进,游戏的推进速度也会随之变快,这个时候如果自身血量小于等于0那么游戏就会结束,整个界面再次下滑,出现游戏结束后所得分数界面GameOverScreen,在这个界面我们还可以再一次启动程序,当点击新游戏时,整个界面上滑,又出现开始游戏画面GameCanvas。这个时候关于界面上滑和下滑就出现了一个循环,即GameCanvas和GameOverScreen相互转化。其实整个游戏画面是一个高度为当前游戏窗口高度不到3倍大的画布(游戏信息数据在游戏总和游戏结束均有)。

3、模型分析

  在这个小游戏中,总共有5个模型,分别是:海藻、星星、带鱼的泡泡、章鱼和螃蟹。除过带鱼的泡泡其余4个模型都有一个公有的基类TowerBase,因此这4个模型我就重点解释其中一个模型,其他模型的代码中也有大量的注释。

  Bomb是海藻组件,其中有一个关键对象SpriteSequence,他可以控制多个动画的渲染,在这个海藻组件中默认使用name为idle的Sprite,这是一个可以将png分段展示的对象。游戏中游戏推进时,fire接口会被调用,播放海藻爆炸前音频文件,当前动画改为shoot,并开启一个定时器,用于调用finishFire接口,完成击杀气泡拯救小鱼的操作,海藻组件代码如下,代码中亦有大量注释

 1 //海藻爆炸  
 2 import QtQuick 2.0
 3 import "../logic.js" as Logic
 4 import ".."
 5 
 6 TowerBase {
 7     id: container
 8     hp: 10
 9     range: 0.4
10     rof: 10
11     property real detonationRange: 2.5
12 
13     function fire() {//开始爆炸
14         sound.play()//首先播放声音
15         sprite.jumpTo("shoot")//
16         animDelay.start()//启动定时器
17     }
18 
19     function finishFire() {//爆炸结束
20         var sCol = Math.max(0, col - 1)
21         var eCol = Math.min(Logic.gameState.cols - 1, col + 1)
22         var killList = new Array()
23         for (var i = sCol; i <= eCol; i++) {
24             for (var j = 0; j < Logic.gameState.mobs[i].length; j++)
25                 if (Math.abs(Logic.gameState.mobs[i][j].y - container.y) < Logic.gameState.squareSize * detonationRange)
26                     killList.push(Logic.gameState.mobs[i][j])//满足爆炸距离的都加入到击杀列表
27             while (killList.length > 0)
28                 Logic.killMob(i, killList.pop())//调用js函数击杀指定列所有目标
29         }
30         Logic.killTower(row, col);//移除海藻
31     }
32 
33     Timer {
34         id: animDelay
35         running: false
36         interval: shootState.frameCount * shootState.frameDuration//动画播放总时长
37         onTriggered: finishFire()
38     }
39 
40     function die()//销毁对象本身
41     {
42         destroy() // No blink, because we usually meant to die
43     }
44 
45     SoundEffect {//播放音频文件
46         id: sound
47         source: "../audio/bomb-action.wav"//海藻爆炸音频文件
48     }
49 
50     SpriteSequence {//动画序列
51         id: sprite
52          64
53         height: 64
54         interpolate: false
55         goalSprite: ""
56 
57         Sprite {//海藻转向动画
58             name: "idle"
59             source: "../gfx/bomb-idle.png"
60             frameCount: 4
61             frameDuration: 800//每帧持续时长
62         }
63 
64         Sprite {//海藻爆炸动画
65             id: shootState
66             name: "shoot"
67             source: "../gfx/bomb-action.png"
68             frameCount: 6
69             frameDuration: 155
70             to: { "dying" : 1 } //动画结束后  跳转到dying动画
71         }
72 
73         Sprite {//海藻爆炸动画
74             name: "dying"
75             source: "../gfx/bomb-action.png"//资源地址
76             frameCount: 1//只包含一帧
77             frameX: 64 * 5
78             frameWidth: 64
79             frameHeight: 64
80             frameDuration: 155
81         }
82 
83         SequentialAnimation on x {//动画作用于x坐标
84             loops: Animation.Infinite
85             NumberAnimation { from: x; to: x + 4; duration: 900; easing.type: Easing.InOutQuad }
86             NumberAnimation { from: x + 4; to: x; duration: 900; easing.type: Easing.InOutQuad }
87         }
88         SequentialAnimation on y {//动画作用于y坐标
89             loops: Animation.Infinite
90             NumberAnimation { from: y; to: y - 4; duration: 900; easing.type: Easing.InOutQuad }
91             NumberAnimation { from: y - 4; to: y; duration: 900; easing.type: Easing.InOutQuad }
92         }
93     }
94 }

 4、js文件分析

  js文件用于控制qml程序的逻辑实现部分,简单的js代码可以内联到qml组件代码里,如果是复杂的或者一些工具函数,那么最好还是写到一个单独的js文件,然后通过import导入到qml文件中。

  关于这个游戏的一些具体细节我个人没有仔细研究,比如游戏的速度控制。写此分析文章的原因主要是为了学习qml的语法和代码习惯,因此不尽如人意的地方可能会比较多,大神勿喷,仅供初学者借鉴。

  这个游戏的js文件主要是提供了一系列的工具函数,包括游戏进度控制的参数,具体接口含义可直接看如下代码中的注释

  1 .pragma library // 共享库  该文件只会被加载一次
  2 .import QtQuick 2.0 as QQ
  3 
  4 // Game Stuff
  5 var gameState // Local reference
  6 function getGameState() { return gameState; }
  7 
  8 var towerData = [ // Name and cost, stats are in the delegate per instance
  9     { "name": "Melee", "cost": 20 },
 10     { "name": "Ranged", "cost": 50 },
 11     { "name": "Bomb", "cost": 75 },
 12     { "name": "Factory", "cost": 25 }
 13 ]
 14 
 15 var waveBaseData = [300, 290, 280, 270, 220, 180, 160, 80, 80, 80, 30, 30, 30, 30];
 16 var waveData = [];
 17 
 18 var towerComponents = new Array(towerData.length);
 19 var mobComponent = Qt.createComponent("mobs/MobBase.qml");
 20 
 21 //游戏结束  释放动态申请的内存空间  并重置相应的标志
 22 function endGame()
 23 {
 24     gameState.gameRunning = false;//游戏未在正在运行
 25     gameState.gameOver = true;//游戏结束
 26     for (var i = 0; i < gameState.cols; i++) {
 27         for (var j = 0; j < gameState.rows; j++) {
 28             if (gameState.towers[towerIdx(i, j)]) {
 29                 gameState.towers[towerIdx(i, j)].destroy();
 30                 gameState.towers[towerIdx(i, j)] = null;
 31             }
 32         }
 33         for (var j in gameState.mobs[i])
 34             gameState.mobs[i][j].destroy();
 35         gameState.mobs[i].splice(0,gameState.mobs[i].length); //Leaves queue reusable
 36     }
 37 }
 38 
 39 function startGame(gameCanvas)
 40 {
 41     waveData = new Array();
 42     for (var i in waveBaseData)
 43         waveData[i] = waveBaseData[i];
 44     gameState.freshState();//重置游戏资料
 45     for (var i = 0; i < gameCanvas.cols; i++) {//清空游戏内存数据,主要针对每个格子存放数据
 46         for (var j = 0; j < gameCanvas.rows; j++)
 47             gameState.towers[towerIdx(i, j)] = null;
 48         gameState.mobs[i] = new Array();
 49     }
 50     gameState.towers[towerIdx(0, 0)] = newTower(3, 0, 0);//左上角生成一个星星
 51     gameState.gameRunning = true;
 52     gameState.gameOver = false;
 53 }
 54 
 55 function newGameState(gameCanvas)//开始一场新游戏
 56 {
 57     for (var i = 0; i < towerComponents.length; i++) {
 58         towerComponents[i] = Qt.createComponent("towers/" + towerData[i].name + ".qml");
 59         if (towerComponents[i].status == QQ.Component.Error) {
 60             gameCanvas.errored = true;
 61             gameCanvas.errorString += "Loading Tower " + towerData[i].name + "
" + (towerComponents[i].errorString());
 62             console.log(towerComponents[i].errorString());
 63         }
 64     }
 65     gameState = gameCanvas;//gameState赋初值  这个时候才知道对象类型
 66     gameState.freshState();//重置游戏数据
 67     gameState.towers = new Array(gameCanvas.rows * gameCanvas.cols);//为游戏分配rows*cols个内存空间,用于存储每个格子数据
 68     gameState.mobs = new Array(gameCanvas.cols);//
 69     return gameState;
 70 }
 71 
 72 function row(y)//返回所在行
 73 {
 74     return Math.floor(y / gameState.squareSize);
 75 }
 76 
 77 function col(x)//返回所在列
 78 {
 79     return Math.floor(x / gameState.squareSize);
 80 }
 81 
 82 function towerIdx(x, y)//根据行和列计算towers位置
 83 {
 84     return y + (x * gameState.rows);
 85 }
 86 
 87 function newMob(col)//随机产生一个带鱼气泡
 88 {
 89     var ret = mobComponent.createObject(gameState.canvas,
 90         { "col" : col,
 91           "speed" : (Math.min(2.0, 0.10 * (gameState.waveNumber + 1))),
 92           "y" : gameState.canvas.height });
 93     gameState.mobs[col].push(ret);
 94     return ret;
 95 }
 96 
 97 function newTower(type, row, col)//根据类型生成模型,并设置模型所在行和列
 98 {
 99     var ret = towerComponents[type].createObject(gameState.canvas);
100     ret.row = row;
101     ret.col = col;
102     ret.fireCounter = ret.rof;
103     ret.spawn();
104     return ret;
105 }
106 
107 function buildTower(type, x, y)//根据菜单项类型、行和列  生成tower
108 {
109     if (gameState.towers[towerIdx(x,y)] != null) {//如果之前存在
110         if (type <= 0) {//如果点击类型不在4个菜单项里 则清空该tower
111             gameState.towers[towerIdx(x,y)].sell();
112             gameState.towers[towerIdx(x,y)] = null;
113         }
114     } else {
115         if (gameState.coins < towerData[type - 1].cost)//如果金额不够 直接退出
116             return;
117         gameState.towers[towerIdx(x, y)] = newTower(type - 1, y, x);//生成一个新的模型
118         gameState.coins -= towerData[type - 1].cost;//减去建造模型 所需要的金币
119     }
120 }
121 
122 function killMob(col, mob)//移除指定列模型
123 {
124     if (!mob)
125         return;
126     var idx = gameState.mobs[col].indexOf(mob);
127     if (idx == -1 || !mob.hp)
128         return;
129     mob.hp = 0;
130     mob.die();
131     gameState.mobs[col].splice(idx,1);//从列中减掉
132 }
133 
134 function killTower(row, col)//销毁指定位置模型
135 {
136     var tower = gameState.towers[towerIdx(col, row)];
137     if (!tower)
138         return;
139     tower.hp = 0;
140     tower.die();
141     gameState.towers[towerIdx(col, row)] = null;
142 }
143 
144 function tick()//游戏推进
145 {
146     if (!gameState.gameRunning)//游戏不在运行时  直接返回
147         return;
148 
149     // Spawn
150     gameState.waveProgress += 1;//游戏推进
151     var i = gameState.waveProgress;
152     var j = 0;
153     while (i > 0 && j < waveData.length)
154         i -= waveData[j++];
155     if ( i == 0 ) // Spawn a mob//生成一个气泡
156         newMob(Math.floor(Math.random() * gameState.cols));
157     if ( j == waveData.length ) { // Next Wave
158         gameState.waveNumber += 1;//游戏等级+1
159         gameState.waveProgress = 0;
160         var waveModifier = 10; // Constant governing how much faster the next wave is to spawn (not fish speed)
161         for (var k in waveData ) // Slightly faster
162             if (waveData[k] > waveModifier)
163                 waveData[k] -= waveModifier;
164     }
165 
166     // 遍历所有格子 
167     for (var j in gameState.towers) {
168         var tower = gameState.towers[j];
169         if (tower == null)
170             continue;
171         if (tower.fireCounter > 0) {
172             tower.fireCounter -= 1;
173             continue;
174         }
175         var column = tower.col;//遍历所有气泡  
176         for (var k in gameState.mobs[column]) {
177             var conflict = gameState.mobs[column][k];
178             if (conflict.y <= gameState.canvas.height && conflict.y + conflict.height > tower.y
179                 && conflict.y - ((tower.row + 1) * gameState.squareSize) < gameState.squareSize * tower.range) { // 满足伤害距离
180                 tower.fire();//
181                 tower.fireCounter = tower.rof;
182                 conflict.hit(tower.damage);//气泡自行处理伤害动作
183             }
184         }
185 
186         // 只有星星模型满足此条件  新增金币  并调用星星的fire动作
187         if (tower.income) {
188             gameState.coins += tower.income;
189             tower.fire();
190             tower.fireCounter = tower.rof;
191         }
192     }
193 
194     // 气泡移动
195     for (var i = 0; i < gameState.cols; i++) {//遍历所有列
196         for (var j = 0; j < gameState.mobs[i].length; j++) {//遍历每一列的气泡
197             var mob = gameState.mobs[i][j];//气泡
198             var newPos = gameState.mobs[i][j].y - gameState.mobs[i][j].speed;
199             if (newPos < 0) {//如果浮出水面  
200                 gameState.lives -= 1;//生命值减1
201                 killMob(i, mob);//移除气泡
202                 if (gameState.lives <= 0)//当生命值小于等于0时 游戏结束
203                     endGame();//执行此操作之后 gameRunning状态变为false  则该tick函数会被调用 但不会往下执行
204                 continue;
205             }
206             var conflict = gameState.towers[towerIdx(i, row(newPos))];//拿到指定位置模型
207             if (conflict != null) {
208                 if (mob.y < conflict.y + gameState.squareSize)
209                     gameState.mobs[i][j].y += gameState.mobs[i][j].speed * 10; // 气泡上浮一下
210                 if (mob.fireCounter > 0) {
211                     mob.fireCounter--;
212                 } else {//气泡移动到守卫模型跟前
213                     gameState.mobs[i][j].fire();//
214                     conflict.hp -= mob.damage;//守卫模型受到伤害
215                     if (conflict.hp <= 0)//当守卫模型血量小于等于0时 移除守卫模型
216                         killTower(conflict.row, conflict.col);
217                     mob.fireCounter = mob.rof;
218                 }
219             } else {
220                 gameState.mobs[i][j].y = newPos;//气泡移动到新位置
221             }
222         }
223     }
224 }

5、左键菜单项

 1 //游戏中左键菜单项
 2 import QtQuick 2.0
 3 import "logic.js" as Logic
 4 
 5 Item {
 6     id: container
 7      64
 8     height: 64
 9     property alias source: img.source
10     property int index//当前点击列序
11     property int row: 0//当前菜单项所在行
12     property int col: 0//当前菜单项所在列
13     property int towerType//表明菜单项类型 根据此类型 可以获取name和cost值
14     property bool canBuild: true//菜单项是否可以被创建
15     property Item gameCanvas: parent.parent.parent
16     signal clicked()//自定义信号  当该控件被点击时 
17 
18     Image {
19         id: img
20         opacity: (canBuild && gameCanvas.coins >= Logic.towerData[towerType-1].cost) ? 1.0 : 0.4//当金币数不够时,该菜单项透明度变为40%
21     }
22     Text {//菜单项右上角数字
23         anchors.right: parent.right
24         font.pointSize: 14
25         font.bold: true
26         color: "#ffffff"
27         text: Logic.towerData[towerType - 1].cost
28     }
29     MouseArea {//鼠标点击时  根据菜单项类型、行数和列数新建模型
30         anchors.fill: parent
31         onClicked: {
32             Logic.buildTower(towerType, col, row)//调用js方法 生成一个新的tower
33             container.clicked()//发出菜单项被点击信号
34         }
35     }
36     Image {//下三角
37         visible: col == index && row != 0 //当列号等于当前点击列时  并且不是第一行
38         source: "gfx/dialog-pointer.png"
39         anchors.top: parent.bottom
40         anchors.topMargin: 4
41         anchors.horizontalCenter: parent.horizontalCenter
42     }
43     Image {//上倒三角
44         visible: col == index && row == 0//当列号等于当前点击列时  并且是第一行
45         source: "gfx/dialog-pointer.png"
46         rotation: 180
47         anchors.bottom: parent.top//三角的底部紧接父控件顶部
48         anchors.bottomMargin: 6
49         anchors.horizontalCenter: parent.horizontalCenter
50     }
51 }

6、主qml,用于整个程序ui布局

  1 //程序主文件,由此qml启动整个ui
  2 import QtQuick 2.0
  3 import QtQuick.Particles 2.0
  4 import "content"
  5 import "content/logic.js" as Logic
  6 
  7 Item {
  8     id: root
  9      320
 10     height: 480
 11     property var gameState//游戏状态  就是游戏场景GameCanvas 维护了大量游戏过程的信息
 12     property bool passedSplash: false//游戏开始标志  只有首次启动游戏时为false 后续游戏结束重新开始依赖于gameOver状态
 13 
 14     Image {
 15         source:"content/gfx/background.png"  //背景图高度为主窗口3倍大小   1:失败重新开始   2:游戏中   3:启动游戏 
 16         anchors.bottom: view.bottom//背景图和窗口底部对齐
 17 
 18         ParticleSystem {//粒子系统
 19             id: particles
 20             anchors.fill: parent
 21 
 22             ImageParticle {//图片粒子   表示气泡
 23                 id: bubble
 24                 anchors.fill: parent
 25                 source: "content/gfx/catch.png"
 26                 opacity: 0.25//透明度25%
 27             }
 28 
 29             Wander {
 30                 xVariance: 25;
 31                 pace: 25;
 32             }
 33 
 34             Emitter {//粒子发射器   规定发射粒子规则
 35                  parent.width
 36                 height: 150
 37                 anchors.bottom: parent.bottom
 38                 anchors.bottomMargin: 3
 39                 startTime: 15000  //每隔15s发送一次粒子
 40 
 41                 emitRate: 2//每次发送粒子数目  默认每秒钟发送10个
 42                 lifeSpan: 15000    //最多存活15s
 43 
 44                 acceleration: PointDirection{ y: -6; xVariation: 2; yVariation: 2 }//加速度    y值减小  x方向和y方向存在2个偏移
 45 
 46                 size: 24//初始大小
 47                 sizeVariation: 16//大小可上下浮动范围
 48             }
 49         }
 50     }
 51 
 52     Column {
 53         id: view
 54         y: -(height - 480)
 55          320
 56         
 57         //游戏结束   对应背景图中序号1
 58         GameOverScreen { gameCanvas: canvas }//绑定GameCanvas到gameCanvas导出对象上,主要为了获取游戏结束时,救了多少条鱼和修改游戏状态
 59 
 60         //游戏中
 61         Item {
 62             id: canvasArea
 63              320
 64             height: 480
 65 
 66             Row {//游戏中  顶部波浪1
 67                 height: childrenRect.height
 68                 Image {
 69                     id: wave
 70                     y: 30//距离顶部30像素
 71                     source:"content/gfx/wave.png"//960*70
 72                 }
 73                 Image {
 74                     y: 30//距离顶部30像素
 75                     source:"content/gfx/wave.png"
 76                 }
 77                 NumberAnimation on x { from: 0; to: -(wave.width); duration: 16000; loops: Animation.Infinite }//向左走
 78                 SequentialAnimation on y {//平方向移动的过程中,垂直方向进行序列动画
 79                     loops: Animation.Infinite
 80                     NumberAnimation { from: y - 2; to: y + 2; duration: 1600; easing.type: Easing.InOutQuad }
 81                     NumberAnimation { from: y + 2; to: y - 2; duration: 1600; easing.type: Easing.InOutQuad }
 82                 }
 83             }
 84 
 85             Row {//游戏中  顶部波浪2
 86                 opacity: 0.5
 87                 Image {
 88                     id: wave2
 89                     y: 25
 90                     source: "content/gfx/wave.png"
 91                 }
 92                 Image {
 93                     y: 25
 94                     source: "content/gfx/wave.png"
 95                 }
 96                 NumberAnimation on x { from: -(wave2.width); to: 0; duration: 32000; loops: Animation.Infinite }//向右走
 97                 SequentialAnimation on y {
 98                     loops: Animation.Infinite
 99                     NumberAnimation { from: y + 2; to: y - 2; duration: 1600; easing.type: Easing.InOutQuad }
100                     NumberAnimation { from: y - 2; to: y + 2; duration: 1600; easing.type: Easing.InOutQuad }
101                 }
102             }
103 
104             Image {//阳光照射效果1
105                 source: "content/gfx/sunlight.png"
106                 opacity: 0.02
107                 y: 0
108                 anchors.horizontalCenter: parent.horizontalCenter
109                 transformOrigin: Item.Top//偏移起始位置
110                 SequentialAnimation on rotation {//在图片旋转属性上使用序列动画  即:动画依次执行
111                     loops: Animation.Infinite//动画无限循环
112                     NumberAnimation { from: -10; to: 10; duration: 8000; easing.type: Easing.InOutSine }
113                     NumberAnimation { from: 10; to: -10; duration: 8000; easing.type: Easing.InOutSine }
114                 }
115             }
116 
117             Image {//阳光照射效果2
118                 source: "content/gfx/sunlight.png"
119                 opacity: 0.04
120                 y: 20
121                 anchors.horizontalCenter: parent.horizontalCenter
122                 transformOrigin: Item.Top
123                 SequentialAnimation on rotation {
124                     loops: Animation.Infinite
125                     NumberAnimation { from: 10; to: -10; duration: 8000; easing.type: Easing.InOutSine }
126                     NumberAnimation { from: -10; to: 10; duration: 8000; easing.type: Easing.InOutSine }
127                 }
128             }
129 
130             Image {//背景网格
131                 source: "content/gfx/grid.png"
132                 opacity: 0.5
133             }
134             //游戏场景
135             GameCanvas {
136                 id: canvas
137                 anchors.bottom: parent.bottom
138                 anchors.bottomMargin: 20
139                 x: 32
140                 focus: true
141             }
142 
143             InfoBar { anchors.bottom: canvas.top; anchors.bottomMargin: 6;  parent.width }
144 
145             //3..2..1..go
146             Timer {
147                 id: countdownTimer
148                 interval: 1000
149                 running: root.countdown < 5
150                 repeat: true
151                 onTriggered: root.countdown++//
152             }
153             Repeater {//倒数数据
154                 model: ["content/gfx/text-blank.png", "content/gfx/text-3.png", "content/gfx/text-2.png", "content/gfx/text-1.png", "content/gfx/text-go.png"]
155                 delegate: Image {
156                     visible: root.countdown <= index
157                     opacity: root.countdown == index ? 0.5 : 0.1
158                     scale: root.countdown >= index ? 1.0 : 0.0
159                     source: modelData
160                     Behavior on opacity { NumberAnimation {} }
161                     Behavior on scale { NumberAnimation {} }//NumberAnimation继承自PropertyAnimation,duration值默认为250ms
162                 }
163             }
164         }
165         //游戏启动页  仅仅包含游戏标题、带气泡的鱼和开始按钮   背景色和上浮的气泡是由根元素下的粒子系统(particles)完成
166         NewGameScreen {
167             onStartButtonClicked: root.passedSplash = true//游戏开始
168         }
169     }
170 
171     property int countdown: 10
172     Timer {
173         id: gameStarter
174         interval: 4000
175         running: false
176         repeat: false
177         onTriggered: Logic.startGame(canvas);//等待4s中启动新的一局  等待的过程中countdownTimer定时器 每隔1s更新倒数图片  显示3 2 1 go
178     }
179 
180     states: [
181         State {//游戏开始   当在游戏第一次启动时gameOver为false   passedSplash为false
182             name: "gameOn"; 
183             when: gameState.gameOver == false && passedSplash    //当游戏状态不为结束并且passedSplash为真时触发  passedSplash值由NewGameScreen信号的处理槽函数修改
184             PropertyChanges { target: view; y: -(height - 960) }//480标示显示第二页
185             StateChangeScript { script: root.countdown = 0; }//countdown重置为0  开始新游戏时  倒数3 2 1
186             PropertyChanges { target: gameStarter; running: true }//调用游戏开始定时器  启动游戏
187         },
188         State {//对应背景图第1页
189             name: "gameOver"; 
190             when: gameState.gameOver == true //当游戏状态为结束时触发
191             PropertyChanges { target: view; y: 0 }//Column定位器滚动到最顶端   也即游戏结束,请重新开始页面
192         }
193     ]
194 
195     transitions: Transition {//对指定属性进行动画过渡
196         NumberAnimation { properties: "x,y"; duration: 1200; easing.type: Easing.OutQuad }
197     }
198     //组件加载完毕时,背景图定位到第三页,即游戏启动页
199     Component.onCompleted: gameState = Logic.newGameState(canvas);
200 }

3、源码下载

  qml maroon小游戏

原文地址:https://www.cnblogs.com/swarmbees/p/6475993.html