qml demo分析(samegame-拼图游戏)

 一、效果展示

  相信大家都玩儿过连连看游戏,而且此款游戏也是闲时一款打发时间的趣事,那么接下来我将分析一款类似的游戏,完全使用qml编写界面,复杂逻辑使用js完成。由于此游戏包含4种游戏模式,因此本篇文章可能会比较长,本篇文章我主要是分析该游戏的主题思路和一些比较难理解的模块,文章末尾我会把示例代码的下载链接附上,示例代码是qt5.7示例代码,位于Qt5.7.0_vs2013ExamplesQt-5.7quickdemossamegame目录下,个人知识添加了大量注释在程序中,逻辑几乎没有修改。

  如图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              parent.width
 33             height: parent.height - (140 + Settings.footerHeight)//
 34 
 35             Button {//1 player
 36                  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          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          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
             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                  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 }

三、下载链接

   samegame示例分析

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