• 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示例分析

  • 相关阅读:
    linux常用命令整理
    pg_sql常用查询语句整理
    python 爬取媒体文件(使用chrome代理,启动客户端,有防火墙)
    python 爬取媒体文件(无防火墙)
    python读写符号的含义
    python数据分析开发中的常用整理
    wget: 无法解析主机地址
    ## nginx 使用
    iptables防火墙
    【redis】Could not connect to Redis at 127.0.0.1:6379: Connection refused
  • 原文地址:https://www.cnblogs.com/swarmbees/p/6560218.html
Copyright © 2020-2023  润新知