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 width: 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 + "\n" + (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 width: 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 width: 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 width: 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 width: 320 56 57 //游戏结束 对应背景图中序号1 58 GameOverScreen { gameCanvas: canvas }//绑定GameCanvas到gameCanvas导出对象上,主要为了获取游戏结束时,救了多少条鱼和修改游戏状态 59 60 //游戏中 61 Item { 62 id: canvasArea 63 width: 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; width: 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 }