一、效果展示
相信大家都玩儿过连连看游戏,而且此款游戏也是闲时一款打发时间的趣事,那么接下来我将分析一款类似的游戏,完全使用qml编写界面,复杂逻辑使用js完成。由于此游戏包含4种游戏模式,因此本篇文章可能会比较长,本篇文章我主要是分析该游戏的主题思路和一些比较难理解的模块,文章末尾我会把示例代码的下载链接附上,示例代码是qt5.7示例代码,位于Qt5.7.0_vs2013\Examples\Qt-5.7\quick\demos\samegame目录下,个人知识添加了大量注释在程序中,逻辑几乎没有修改。
如图1所示,是这个游戏简单的示意,要想展示完全合格游戏的所有细节,仅仅靠这一个小的gif图是完全不够的,因此本篇文章不会和以往的示例分析那样,在源码分析过程中没有程序截图,在后续代码分析中我会适当的加入一些游戏过程中的截图。
图1 samgegame简单试玩
二、源码分析
细心的同学可能会发现在1 player模式下,程序出现了最高分的显示,呵呵呵。。。因为那是我玩儿的,这个游戏也使用了简单的本地数据库存储功能,在后续代码分析过程当中这一部分我会单独拉出来讲解。
还是同上一篇文章一样,我们还是先来整体分析下工程目录,文件看起来真是多啊,起初我看到这么多文件的时候自己也是挺难以下手,不过当我正真的理解这个程序的设计意图之后,想想就没有那么难了。
图2 工程目录
既然我已经把这个示例代码做了整体的理解,那么我就不会那么无脑的一个一个文件讲解,首先我要做的就是把这么写qml进行分类,分类完以后的工程目录就没有图2那么恐怖了,如图3所示是按功能点整理后的工程目录
图3 整理后工程目录
接下来我按文件夹分别说明该模块下的qml文件功能
- data:主要负责puzzle模式下提供关卡数据
- emitter:重写了一些例子发射器,每个例子发射器所属组不同,并且设置有不同的参数,例如:例子发射速率、例子生命周期等
- block:色块,分别存储了4种游戏模式下的色块
- control:封装了该示例代码公有的一部分组件,例如Button是封装过后的按钮,点击时可以发出clicked信号
- setting:程序设置,包括初始大小,一些基础布局信息
- 其余:剩下3个qml文件,也是游戏画面中比较关键的积分qml
1、页面布局
一般情况下qml主文件的名字都和工程名字相同,那么本篇示例程序的主程序文件就是samegame.qml文件无疑。由于该文件代码量过大,我就不整篇粘贴了,大家可以在自己安装的qt库的example目录下自行查找,如下代码所示是我对主程序文件进行了大量删减得出的主界面布局,程序中几乎每一行代码都有注释,包括删减的代码依然有大量注释,感兴趣的同学可到文章最后提供的链接进行下载
1 import QtQuick 2.0 2 import QtQuick.Particles 2.0 3 import "content/samegame.js" as Logic 4 import "content"//导入一个目录 目录下的所有组件可以直接被使用 5 6 Rectangle { 7 Image {//主窗口背景色 底部菜单栏和顶部信息面板都在该背景图之上 8 source: "content/gfx/background.png" 9 anchors.fill: parent 10 } 11 GameArea {//游戏画布 12 } 13 Item {//游戏启动界面 主菜单 除过底部工具栏以外部分 14 //G和S动画 15 LogoAnimation { 16 x: 64 17 y: Settings.headerHeight 18 particleSystem: gameCanvas.ps 19 running: root.state == ""//游戏未开始时运行 20 } 21 //Game和Same剩余的3个字母 22 Row { 23 x: 112 24 y: 20 25 Image { source: "content/gfx/logo-a.png" }//A 26 Image { source: "content/gfx/logo-m.png" }//M 27 Image { source: "content/gfx/logo-e.png" }//E 28 } 29 //四种游戏模式 30 Column { 31 spacing: Settings.menuButtonSpacing//在context目录下Settings组件中定义的属性 32 width: parent.width 33 height: parent.height - (140 + Settings.footerHeight)// 34 35 Button {//1 player 36 width: root.width 37 rotatedButton: true//按钮1支持旋转 38 imgSrc: "content/gfx/but-game-1.png"//根据导出属性指定图片地址(相对路径指定方式) 39 onClicked: {//自定义信号对于槽 40 if (root.state == "in-game")//如果游戏中则什么事情都不干 41 return //Prevent double clicking 42 root.state = "in-game"//游戏开始后 将游戏状态置位in-game字样 43 gameCanvas.blockFile = "Block.qml"//指定游戏中每个格子实例化时所引用的组件 44 gameCanvas.background = "gfx/background.png"//指定游戏背景色 45 arcadeTimer.start(); 46 } 47 //Emitted particles don't fade out, because ImageParticle is on the GameArea 48 system: gameCanvas.ps//按钮中粒子发射器所属粒子系统 49 group: "green"//按钮中粒子发射器所属组 50 Timer { 51 id: arcadeTimer 52 interval: Settings.menuDelay 53 running : false 54 repeat : false 55 onTriggered: Logic.startNewGame(gameCanvas)//启动一次新游戏 56 } 57 } 58 59 Button {//2 players 60 } 61 Button {//zen 62 } 63 Button {//puzzle 64 } 65 } 66 } 67 Image {//顶部游戏信息面盘 68 id: scoreBar 69 source: "content/gfx/bar.png" 70 width: parent.width 71 z: 6 72 y: -Settings.headerHeight//默认不显示 在界面以外 73 height: Settings.headerHeight 74 Behavior on opacity { NumberAnimation {} } 75 SamegameText {//当前游戏分数 arcade下显示 76 id: arcadeScore 77 anchors { 78 right: parent.right; //从界面右侧开始布局 79 topMargin: 3; 80 rightMargin: 11; 81 top: parent.top 82 } 83 text: '<font color="#f7d303">P1:</font> ' + gameCanvas.score//黄色的P1字样+白色字样的游戏分数 84 font.pixelSize: Settings.fontPixelSize 85 textFormat: Text.StyledText//字体格式 86 color: "white"//分数颜色为白色 87 opacity: gameCanvas.mode == "arcade" ? 1 : 0//游戏模式为arcade时 显示 88 Behavior on opacity { NumberAnimation {} }//透明度使用渐变 89 } 90 SamegameText {//最高分 arcade下显示 91 } 92 SamegameText {//玩家一 得分 多人下显示 93 } 94 SamegameText {//玩家二 得分 多人下显示 95 } 96 SamegameText {//移动步数 puzzle下显示 97 } 98 SamegameText {//当前游戏时长 puzzle下显示 99 } 100 SamegameText {//游戏得分 puzzle下显示 101 } 102 } 103 104 Image {//底部工具条 105 id: bottomBar 106 width: parent.width 107 height: Settings.footerHeight 108 source: "content/gfx/bar.png" 109 y: parent.height - Settings.footerHeight; 110 z: 2 111 Button {//退出按钮 112 id: quitButton 113 height: Settings.toolButtonHeight 114 imgSrc: "content/gfx/but-quit.png" 115 onClicked: {Qt.quit(); }//点击退出应用程序 116 anchors { left: parent.left; verticalCenter: parent.verticalCenter; leftMargin: 11 } 117 } 118 Button {//菜单按钮 返回主页 119 } 120 Button {//开始新的一局 121 } 122 Button {//puzzle模式下 下一关 123 } 124 } 125 }
程序根节点是一个Rectangle组件,该组件是整个程序的根,他含有一个总的背景色。然后是游戏画布,游戏画布即除过顶部信息面板和底部工具栏以外的区域,游戏画布顶端紧接着顶部信息面板,底部紧接着底部工具栏顶端。从代码顺序往下分析,然后紧接着的两个大的Item分别就是顶部信息面板和底部工具条,他们两个的id分别是scoreBar与bottomBar。
GameArea组件几乎包含了游戏过程中所有的ui交互操作,在该组件中包含有一个粒子系统,游戏当中所有的烟花效果都是由该粒子系统进行实现,粒子系统默认有一种粒子图片,但是其使用Loader还加载了8种粒子图片,以备不同的粒子发射器使用,值得注意的是粒子发射器可以发射同属一个组的粒子图片。
//游戏窗口 import QtQuick 2.0 import QtQuick.Particles 2.0 import "samegame.js" as Logic import "." Item { Image {//游戏中背景色 id: bg z: -1 anchors.fill: parent source: background; fillMode: Image.PreserveAspectCrop } MouseArea { anchors.fill: parent; onClicked: { if (puzzleTextBubble.opacity == 1) { puzzleTextBubble.opacity = 0;//隐藏关卡提示窗口 Logic.finishLoadingMap();//根据本地文件数据 加载puzzle游戏数据 } else if (!swapping) { Logic.handleClick(mouse.x,mouse.y);//处理点击事件 } } } Image {//历史最高分提示窗口 id: highScoreTextBubble opacity: mode == "arcade" && gameOver && gameCanvas.score == gameCanvas.highScore ? 1 : 0 Behavior on opacity { NumberAnimation {} } anchors.centerIn: parent z: 10 source: "gfx/bubble-highscore.png" Image { anchors.centerIn: parent source: "gfx/text-highscore-new.png" rotation: -10 } } Image {//过关提示 蓝色背景提示 id: puzzleTextBubble anchors.centerIn: parent opacity: 0 Behavior on opacity { NumberAnimation {} } z: 10 source: "gfx/bubble-puzzle.png" Connections { target: gameCanvas onModeChanged: if (mode != "puzzle" && puzzleTextBubble.opacity > 0) puzzleTextBubble.opacity = 0; } Text {//文本信息 id: puzzleTextLabel width: parent.width - 24 anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter color: "white" font.pixelSize: 24 font.bold: true wrapMode: Text.WordWrap } } onModeChanged: {//当前游戏模式发送变化 隐藏双人模式下获胜窗口 p1WonImg.opacity = 0; p2WonImg.opacity = 0; } SmokeText { id: puzzleWin; source: "gfx/icon-ok.png"; system: particleSystem }//puzzle模式闯关胜利 SmokeText { id: puzzleFail; source: "gfx/icon-fail.png"; system: particleSystem }//puzzle模式闯关失败 onSwapPlayers: {//双人模式下 鼠标点击后 交换玩家 smokeParticle.color = "yellow" Logic.turnChange();//交换玩家 if (curTurn == 1) { p1Text.play(); } else { p2Text.play(); } clickDelay.running = true; } SequentialAnimation { id: clickDelay//点击延迟 防止点击速度过快 ScriptAction { script: gameCanvas.swapping = true; }// PauseAnimation { duration: 750 } ScriptAction { script: gameCanvas.swapping = false; } } SmokeText {//提示玩家1 可以操作 id: p1Text; source: "gfx/text-p1-go.png"; system: particleSystem; playerNum: 1 opacity: p1WonImg.opacity + p2WonImg.opacity > 0 ? 0 : 1 } SmokeText {//提示玩家2 可以操作 id: p2Text; source: "gfx/text-p2-go.png"; system: particleSystem; playerNum: 2 opacity: p1WonImg.opacity + p2WonImg.opacity > 0 ? 0 : 1 } onGameOverChanged: {//游戏结束 if (gameCanvas.mode == "multiplayer") { if (gameCanvas.score >= gameCanvas.score2) { p1WonImg.opacity = 1; } else { p2WonImg.opacity = 1; } } } Image {//提示玩家1获胜 id: p1WonImg source: "gfx/text-p1-won.png" anchors.centerIn: parent opacity: 0 Behavior on opacity { NumberAnimation {} } z: 10 } Image {//提示玩家2获胜 id: p2WonImg source: "gfx/text-p2-won.png" anchors.centerIn: parent opacity: 0 Behavior on opacity { NumberAnimation {} } z: 10 } ParticleSystem{//粒子系统 id: particleSystem; anchors.fill: parent z: 5 ImageParticle { id: smokeParticle groups: ["smoke"] source: "gfx/particle-smoke.png" alpha: 0.1 alphaVariation: 0.1 color: "yellow" } Loader { id: auxLoader anchors.fill: parent source: "PrimaryPack.qml" onItemChanged: { if (item && "particleSystem" in item) item.particleSystem = particleSystem if (item && "gameArea" in item) item.gameArea = gameCanvas } } } }
程序中包含有大量SmokeText组件对象和SmokeText组件对象,这些组件都被封装了起来,可以实现特定功能,例如SmokeText是烟花文本,首先提供文本显示功能,随后文本隐藏,并伴随烟花效果。该模块有一个MouseArea组件,铺满了整个GameArea区域,他主要是为了处理游戏过程中的鼠标点击事件。
ui展示的最后一个东西就是LogoAnimatin文件了,该文件代码量不大,因此我没有做删减,这个组件实现了一个G和S滚动的效果,如图1中的gis所展示的效果那样。
1 //程序启动窗口 game和same字样中 g/s切换动画 2 import QtQuick 2.0 3 import QtQuick.Particles 2.0 4 5 Item { 6 id: container //Positioned where the 48x48 S/G should be 7 property alias running: mainAnim.running 8 property ParticleSystem particleSystem 9 property int dur: 500 10 signal boomTime 11 Image {//S字样 12 id: s1 13 source: "gfx/logo-s.png" 14 y: 0 15 } 16 Image {//G字样 17 id: g1 18 source: "gfx/logo-g.png" 19 y: -128 20 } 21 Column {//垂直布局2个元素 以便产生粒子效果 22 Repeater { 23 model: 2 24 Item { 25 width: 48 26 height: 48 27 BlockEmitter { 28 id: emitter 29 anchors.fill: parent 30 group: "red" 31 system: particleSystem//粒子发射器所述粒子系统 32 Connections {//链接container对象的信号 33 target: container 34 onBoomTime: emitter.pulse(100); 35 } 36 } 37 } 38 } 39 } 40 SequentialAnimation { 41 id: mainAnim 42 running: true 43 loops: -1//无限循环 44 PropertyAction { target: g1; property: "y"; value: -128}//界面以外 45 PropertyAction { target: g1; property: "opacity"; value: 1}//透明度变为1 46 PropertyAction { target: s1; property: "y"; value: 0} 47 PropertyAction { target: s1; property: "opacity"; value: 1} 48 NumberAnimation { target: g1; property: "y"; from: -96; to: -48; duration: dur}//G字样移动到S字样顶部 49 ParallelAnimation {//G字样和S字样同时移动 50 NumberAnimation { target: g1; property: "y"; from: -48; to: 0; duration: dur} 51 NumberAnimation { target: s1; property: "y"; from: 0; to: 48; duration: dur } 52 } 53 PauseAnimation { duration: dur }//暂停500ms 54 ScriptAction { script: container.boomTime(); }//执行js脚本 发送boomTime信号 id为emitter的BlockEmitter发射器 处理该信号 55 ParallelAnimation {//G字样和S字样同时淡出 56 NumberAnimation { target: g1; property: "opacity"; to: 0; duration: dur } 57 NumberAnimation { target: s1; property: "opacity"; to: 0; duration: dur } 58 } 59 PropertyAction { target: s1; property: "y"; value: -128}//S移动到界面外 60 PropertyAction { target: s1; property: "opacity"; value: 1}//可见 61 NumberAnimation { target: s1; property: "y"; from: -96; to: 0; duration: dur * 2}//将S字样移动到A字母平齐位置 62 } 63 }
代码中有大量注释,相信大家应该都看得懂,值得注意的是32行的Connections连接,他将该组件的boomTime信号进行了处理,让emitter粒子发射器发生了100ms粒子,并进行关闭。代码最后的mainAnim序列动画,将G和S窗口进行了简单的动画处理,其中PropertyAction是属性动作,可以重置属性值,NumberAnimation时属性动画,将指定属性从from态变为to态,并使用duration指定的时间,如果需要指定js代码,则使用ScriptAction脚本动作。
2、data目录
data目录包含的qml文件是最多的,但也缺是最简单的,该目录下的所有文件都是服务于puzzle游戏模式,TemplateBase.qml组件中封装了puzzle游戏模式中的关卡过关评判标准,level*.qml文件都继承自TemplateBase.qml组件,新增了startingGrid属性,用于存储关卡数据,关于其他属性TemplateBase.qml文件中都有具体注释,如图4所示puzzle过关评判标准。
图4 puzzle过关评判标准
3、emitter目录
该目录下包含4个文件,除过PrimaryPack文件外都是重写了粒子发射器,PrimaryPack.qml文件主要是提供了粒子发射器发射的粒子图片,在GameArea组件中通过Loader加载器进行了所有粒子图片的加载处理。
- BlockEmitter.qml组件是块粒子发射器,主要用于启动页G和S色块消失时产生烟花效果
- MenuEmitter.qml组件是块粒子发射器,主要用于启动页菜单项被点击消失时产生烟花效果
- PaintEmitter.qml组件是粒子发射器,主要用于游戏过程中色块消失时提供烟花效果
重写的粒子发射器主要是针对粒子发射器属性进行了重新赋值。如下BlockEmitter粒子发射器所示
1 import QtQuick 2.0 2 import QtQuick.Particles 2.0 3 // Needed for singletons QTBUG-34418 4 import "." 5 6 Emitter { 7 property Item block: parent//父组件 8 velocity: TargetDirection{targetX: block.width/2; targetY: block.height/2; magnitude: -40; magnitudeVariation: 40} 9 acceleration: TargetDirection{targetX: block.width/2; targetY: block.height/2; magnitude: -100;} 10 shape: EllipseShape{fill:true} 11 enabled: false; 12 lifeSpan: 700; //生命周期周期 13 lifeSpanVariation: 100//生命周期振幅 14 emitRate: 1000//速率 每秒钟产生1000个粒子 15 maximumEmitted: 100 //only fires 0.1s bursts (still 2x old number) 16 size: Settings.blockSize * 0.85//粒子初始大小 17 endSize: Settings.blockSize * 0.85 /2//最终大小 18 }
4、block目录
连连看游戏总共包含4种游戏模式,其实1 player和2 players模式使用的色块文件是同一个qml文件。
- block.qml:使用与1 player和2 players游戏模式,该色块文件比SimpleBlock.qml色块文件多了一个PaintEmitter粒子发射器,在游戏过重当中主要表现在色块消失后会在背景色上留下一片阴影,如图5所示色块消失时,在界面上留下了红色的效果,随着时间推进该红色残留会逐渐消失。
- PuzzleBlock.qml:puzzle模式下色块,主要是加载色块图片不一样
- SimpleBlock.qml:类似于block.qml色块,只是色块消失时没有颜色残留,烟花效果依然存在
图5 Block消失演示
5、control目录
单纯的组件封装
- Button.qml:实现了按钮的基本功能,例如主界面上的游戏菜单选项,底部工具栏的按钮均是该组件对象
- SamegameText.qml:Text封装,主要针对Text的一些属性进行了设置
- SmokeText.qml:带有烟花消失效果的文本窗口
6、setting目录
如下代码所示,进行了程序基础值定义
1 //游戏 属性定义 2 import QtQml 2.0 3 4 QtObject { 5 // This height/width is here for desktop testing, otherwise 6 // we could just use Screen.width/Screen.height. 7 property int screenHeight: 1280 8 property int screenWidth: 768 9 10 property int menuDelay: 500 11 12 property int headerHeight: 70 13 property int footerHeight: 100 14 15 property int fontPixelSize: 55 16 17 property int blockSize: 64 18 19 property int toolButtonHeight: 64 20 21 property int menuButtonSpacing: 15 22 }
7、js文件分析
除过游戏ui部分,js文件就是该示例代码的灵魂所在,完成了qml不容易控制的逻辑代码
7.1动态加载组件
1 function changeBlock(src) 2 { 3 blockSrc = src; 4 component = Qt.createComponent(blockSrc); 5 }
7.2创建组件对象
1 function createBlock(column,row,type) 2 { 3 // Note that we don't wait for the component to become ready. This will 4 // only work if the block QML is a local file. Otherwise the component will 5 // not be ready immediately. There is a statusChanged signal on the 6 // component you could use if you want to wait to load remote files. 7 if (component.status == 1){//组件加载完毕Component.Ready 8 if (type == undefined) 9 type = Math.floor(Math.random() * types); 10 if (type < 0 || type > 4) { 11 console.log("Invalid type requested");//TODO: Is this triggered by custom levels much? 12 return; 13 } 14 //通过组件创建对象 15 var dynamicObject = component.createObject(gameCanvas,//父类 16 {"type": type,//导出属性type 17 "x": column*gameCanvas.blockSize, 18 "y": -1*gameCanvas.blockSize, 19 "width": gameCanvas.blockSize, 20 "height": gameCanvas.blockSize, 21 "particleSystem": gameCanvas.ps});//导出属性particleSystem 22 if (dynamicObject == null){ 23 console.log("error creating block"); 24 console.log(component.errorString()); 25 return false; 26 } 27 dynamicObject.y = row*gameCanvas.blockSize; 28 dynamicObject.spawned = true; 29 30 board[index(column,row)] = dynamicObject; 31 }else{ 32 console.log("error loading block component"); 33 console.log(component.errorString()); 34 return false; 35 } 36 return true; 37 }
7.3启动新的一局游戏
1 //开始一场新游戏 gc变量类型决定gameCanvas变量类型 2 function startNewGame(gc, mode, map) 3 { 4 gameCanvas = gc;//初始化当前游戏对象 5 if (mode == undefined) 6 gameMode = "arcade";//默认为arcade游戏模式 7 else 8 gameMode = mode; 9 gameOver = false;//游戏未结束 10 11 cleanUp(); 12 13 gc.gameOver = false; 14 gc.mode = gameMode; 15 // Calculate board size 16 maxColumn = Math.floor(gameCanvas.width/gameCanvas.blockSize);//计算最大列数 17 maxRow = Math.floor(gameCanvas.height/gameCanvas.blockSize);//计算最大行数 18 maxIndex = maxRow * maxColumn;//计算格子个数 19 if (gameMode == "arcade") //Needs to be after board sizing 20 getHighScore();//从本地sqlite数据库获取最佳得分 21 22 23 // Initialize Board 24 board = new Array(maxIndex);//申请游戏格子内存 25 gameCanvas.score = 0;//初始化游戏参数 26 gameCanvas.score2 = 0; 27 gameCanvas.moves = 0; 28 gameCanvas.curTurn = 1; 29 if (gameMode == "puzzle")//如果是puzzle模式 则需要加载关卡数据 30 loadMap(map); 31 else//Note that we load them in reverse order for correct visual stacking 32 for (var column = maxColumn - 1; column >= 0; column--)//循环创建每一个格子上的块 即红色圆形、蓝色圆形或者黄色圆形等 33 for (var row = maxRow - 1; row >= 0; row--) 34 createBlock(column, row); 35 if (gameMode == "puzzle")//如果是puzzle模式 则需要加载历史闯关等级 36 getLevelHistory();//Needs to be after map load 37 gameDuration = new Date();//游戏开始 开始计时 38 }
7.4鼠标点击处理游戏进度
1 function handleClick(x,y) 2 { 3 if (betweenTurns || gameOver || gameCanvas == undefined) 4 return; 5 var column = Math.floor(x/gameCanvas.blockSize); 6 var row = Math.floor(y/gameCanvas.blockSize); 7 if (column >= maxColumn || column < 0 || row >= maxRow || row < 0) 8 return; 9 if (board[index(column, row)] == null)//判断当前点击的块是否为空 10 return; 11 // If it's a valid block, remove it and all connected (does nothing if it's not connected) 12 floodFill(column,row, -1); 13 if (fillFound <= 0) 14 return; 15 if (gameMode == "multiplayer" && gameCanvas.curTurn == 2) 16 gameCanvas.score2 += (fillFound - 1) * (fillFound - 1);//两个玩家时 给玩家2加分 17 else 18 gameCanvas.score += (fillFound - 1) * (fillFound - 1); 19 if (gameMode == "multiplayer" && gameCanvas.curTurn == 2) 20 shuffleUp();//该玩家2时 向上洗牌 21 else 22 shuffleDown();//向下洗牌 23 gameCanvas.moves += 1;//移动次数加一 24 if (gameMode == "endless") 25 refill(); 26 else if (gameMode != "multiplayer") 27 victoryCheck(); 28 if (gameMode == "multiplayer" && !gc.gameOver){ 29 betweenTurns = true; 30 gameCanvas.swapPlayers();//signal, animate and call turnChange() when ready 31 } 32 }
7.5检测游戏是否结束
1 //检测游戏是否结束 2 function victoryCheck() 3 { 4 // Awards bonuses for no blocks left 5 var deservesBonus = true;//额外奖励 6 if (board[index(0,maxRow - 1)] != null || board[index(0,0)] != null)//坐上角和左下角如果有色块 则说明窗口上还有色块 7 deservesBonus = false; 8 // Checks for game over 9 if (deservesBonus){//无色块 10 if (gameCanvas.curTurn = 1)//该哪个玩家 给那个玩家加1000分 11 gameCanvas.score += 1000; 12 else 13 gameCanvas.score2 += 1000; 14 } 15 gameOver = deservesBonus; 16 if (gameCanvas.curTurn == 1){//如果是玩家1 操作 17 if (!(floodMoveCheck(0, maxRow - 1, -1))) 18 gameOver = true; 19 }else{ 20 if (!(floodMoveCheck(0, 0, -1, true))) 21 gameOver = true; 22 } 23 if (gameMode == "puzzle"){ 24 puzzleVictoryCheck(deservesBonus);//Takes it from here 25 return; 26 } 27 if (gameOver) { 28 var winnerScore = Math.max(gameCanvas.score, gameCanvas.score2); 29 if (gameMode == "multiplayer"){ 30 gameCanvas.score = winnerScore;//更新最高分 31 saveHighScore(gameCanvas.score2); 32 } 33 saveHighScore(gameCanvas.score);//保存历史最高分 34 gameDuration = new Date() - gameDuration;//计算游戏耗时 35 gameCanvas.gameOver = true;//游戏结束 36 } 37 }
7.6从本地sqlite数据库读取历史数据
1 //从本地sqlite数据库读取最高分 2 function getHighScore() 3 { 4 var db = Sql.LocalStorage.openDatabaseSync( 5 "SameGame", 6 "2.0", 7 "SameGame Local Data", 8 100 9 ); 10 db.transaction( 11 function(tx) { 12 tx.executeSql('CREATE TABLE IF NOT EXISTS Scores(game TEXT, score NUMBER, gridSize TEXT, time NUMBER)'); 13 // Only show results for the current grid size 14 var rs = tx.executeSql('SELECT * FROM Scores WHERE gridSize = "' 15 + maxColumn + "x" + maxRow + '" AND game = "' + gameMode + '" ORDER BY score desc'); 16 if (rs.rows.length > 0) 17 gameCanvas.highScore = rs.rows.item(0).score; 18 else 19 gameCanvas.highScore = 0; 20 } 21 ); 22 }
7.7保存游戏数据到本地sqlite数据库
1 //保存最高分到本地sqlite数据库 2 function saveHighScore(score) 3 { 4 // Offline storage 5 var db = Sql.LocalStorage.openDatabaseSync( 6 "SameGame", 7 "2.0", 8 "SameGame Local Data", 9 100 10 ); 11 var dataStr = "INSERT INTO Scores VALUES(?, ?, ?, ?)"; 12 var data = [ 13 gameMode, 14 score, 15 maxColumn + "x" + maxRow, 16 Math.floor(gameDuration / 1000) 17 ]; 18 if (score >= gameCanvas.highScore)//Update UI field 19 gameCanvas.highScore = score; 20 21 db.transaction( 22 function(tx) { 23 tx.executeSql('CREATE TABLE IF NOT EXISTS Scores(game TEXT, score NUMBER, gridSize TEXT, time NUMBER)'); 24 tx.executeSql(dataStr, data); 25 } 26 ); 27 }