威尼斯手机平台登陆-官方网站登录

威尼斯手机平台登陆为您带来世界三大博彩公司最新相关资讯,威尼斯官方网站登录充分考虑到不同地域网民的不同需求,威尼斯手机平台登陆良好的用户界面,人性化的操作,实用的功能设计使其广泛受到欢迎,推动实体出版、影视、动漫、游戏等相关文化产业的发展。

您的位置:威尼斯手机平台登陆 > 前端资源 > 【威尼斯登录首页】做完这些后就可以开始游戏开发了,只要点击你想要移动的图块

【威尼斯登录首页】做完这些后就可以开始游戏开发了,只要点击你想要移动的图块

发布时间:2020-04-21 16:52编辑:前端资源浏览(86)

    初学lufylegend.js之日,我用lufylegend.js开发了第一个HTML5小游戏——拼图游戏,还写了篇博文来炫耀一下:HTML5小游戏《智力大拼图》发布,挑战你的思维风暴。不过当时初学游戏开发,经验浅薄,所以没有好好专研游戏里的算法和代码的缺陷,导致游戏出现了很多bug,甚至拼图打乱后很可能无法复原。最近经常有朋友问起这个游戏,希望我能把代码里的bug改一下方便初学者学习,顺便我也打算测试一下自己写这种小游戏的速度,所以就抽出了一些时间将这个游戏从头到尾重新写了一遍,计算了一下用时,从准备、修改素材到最后完成游戏,一共用了大约2h的时间。

    一,准备工作

    本次游戏开发需要用到lufylegend.js开源游戏引擎,版本我用的是1.5.2(现在最新的版本是1.6.0)。

    引擎下载的位置:

    引擎API文档:

    首先为了开发方便,我们得先建立一个叫Find_Word的文件夹,然后在里面添加项目,如下:

    Find_Word文件夹

    |---index.html

     

    |---js文件夹

     

    |---main.js

     

    |---lufylegend-1.5.2.min.js(游戏引擎)

     

    |---lufylegend-1.5.2.js(游戏引擎)

     

    做完这些后就可以开始游戏开发了。

    前言

    最近写了一个 iOS小游戏,纯属一时兴起。动机:那天看到妹妹在朋友圈发了一组图片,正好是九宫格的形状,突然间就觉得这些图片不就像是一个拼图游戏吗?如果可以直接移动玩拼图,那也挺酷哇。撸起袖子就是干!做出来的效果就是这样的:

    威尼斯登录首页 1

    以下是游戏地址:

    二,制作过程

    由于本次游戏开发较为简单,因此,我简单说一下过程。首先,在index.html中加入html代码:

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>html5 game</title>
    <script type="text/javascript" src="./js/lufylegend-1.5.2.min.js"></script>
    <script type="text/javascript" src="./js/main.js"></script> 
    </head>
    <body>
    <div id="mylegend">loading......</div>
    </body>
    </html>
    

    也许有些朋友会很纳闷,因为他们没有找到canvas标签。其实在lufylegend中,当你调用init()函数时,会自动加入canvas标签,因此你只用写下一个div标签和这个div标签的id就能在html中自动加入canvas。

     

    在main.js调用init()的写法如下:

    init(50,"mylegend",525,500,main);
    

    init函数第一个参数是页面刷新频率,第二个是canvas加到的div的id,第三个和第四个是页面尺寸,最后一个是页面初始化完成后调用的函数。

     

    接着,我们定义一连串的变量:

    var backLayer,tileLayer,ctrlLayer,overLayer,gameoverLayer;
    var tileText,overText,gameoverText;
    var col,row;
    var time = 0;
    var selectLayer;
    var checkpoints = [
        ["籍","藉"],
        ["我","找"],
        ["春","舂"],
        ["龙","尤"],
        ["曰","日"]
    ];
    var checkpointNo = 0;
    var i0;
    var j0;
    var i,j;
    var partX,partY;
    var overTextContent = ["恭喜您,您过关了","进入下一关","重新开始"];
    var gameoverTextContent = ["对不起,您失败了","重开关卡"];
    var nowLine;
    var setTimeLine;
    

    其中我只介绍几个重要的变量,其他的以后会提到。

    var backLayer,tileLayer,ctrlLayer,overLayer,gameoverLayer;
    

    这些代码是在定义层变量,方便以后游戏开发。

    var tileText,overText,gameoverText;
    

    这里是在定义游戏中可能出现的字层变量。

    var checkpoints = [
        ["籍","藉"],
        ["我","找"],
        ["春","舂"],
        ["龙","尤"],
        ["曰","日"]
    ];
    

    这些是定义关卡,在这个二维数组中,每一个数组就是一关,每一个数组中的文字就是关卡中要出现的字。可以看出,这个游戏共5关

     

    接下来就是游戏的函数部分。首先是main函数:

    function main(){
        i0 = Math.floor(Math.random()*10);
        j0 = Math.floor(Math.random()*10);
    
        initLayer();
        initCtrl();
        initTile();
    }
    

    在这里面,我首先给i0和j0赋值,让他们成为任何一个0-10之间的随即数,以便确定哪里是不同的那个字。然后我还在这个程序中初始化了层和控制,以及调用了显示文字的函数initTile(),让我们分别来看看initLayer和initTile中的代码:

     

    initLayer中:

    function initLayer(){
        backLayer = new LSprite();
        addChild(backLayer);
    
        tileLayer = new LSprite();
        backLayer.addChild(tileLayer);
    
        ctrlLayer = new LSprite();
        backLayer.addChild(ctrlLayer);
    }
    

    我用lufylegend中LSprite类的方法将层变量定义为了一个容器,以后要显示什么东西,就可以往这些容器中放。其中addChild是把一个东西放进容器的函数,当然放进去的东西也可以是个容器。由此,游戏就有了层次感。如果直接写addChild(xxx)就是把xxx放在游戏最底层。

     

    initTile中:

    function initTile(){
        for(i=0;i<row;i++){
            for(j=0;j<col;j++){
                tile();
            }
        }
    }
    

    这个函数是在进行平铺工作,做法有点像贴瓷砖。关键在于tile(),页面上的东西全都是由它贴上去的。接下来让我们揭示它的真面目:

    function tile(){
        tileLayer.graphics.drawRect(3,"dimgray",[j*50,i*50,50,50],true,"lightgray");
    
        var w = checkpoints[checkpointNo][(i==i0 && j==j0) ? 0 : 1];
        tileText = new LTextField();
        tileText.weight = "bold";
        tileText.text = w;
        tileText.size = 25;
          tileText.color = "dimgray";
        tileText.font = "黑体";
        tileText.x = j*50+7;
        tileText.y = i*50+7;
        tileLayer.addChild(tileText);
    }
    

    首先我们先在页面上把格子平铺好,因此用了lufylegend中LGraphics类中的drawRect,并利用它在屏幕上画了100个50*50正方形。

    drawRect的几个参数分别是:

    第一个:边缘线粗
    第二个:边缘线颜色
    第三个:[起始坐标x,起始坐标y,矩形宽width,矩形高height]
    第四个:是否实心图形
    第五个:实心颜色

    画好格子后,我们开始给每个格子上写文字。在lufylegend中输出文字很简单,只要定义一个LTextField类并给它的属性填值然后将它addChild就可以完成。

    它的属性有:

     

    type 类型
    x 坐标x
    y 坐标y
    text 作为文本字段中当前文本的字符串
    font 文字的格式
    size 文字大小
    color 文字颜色
    visible 是否可见
    weight 文字粗细
    stroke 当为true时,可以设置线宽
    lineWidth 文字线宽
    textAlign 文字左右对齐方式
    textBaseline 文字上下对齐方式

     

    举一个简单的例子方便大家理解:

    var backLayer,title; 
    function main(){ 
        backLayer = new LSprite(); 
        addChild(backLayer); 
        title = new LTextField(); 
        title.size = 30; 
        title.color = "#ff0000"; 
        title.text = "文字显示测试"; 
        backLayer.addChild(title); 
    }
    

    当大家了解完了LTextField类,那理解我的代码就简单了,首先我设定了文字的内容:

    var w = checkpoints[checkpointNo][(i==i0 && j==j0) ? 0 : 1];
    

    这行代码的意思是当画一个方块时,判断画的那一块是第几行第几个,也就是i和j,然后看看是不是和j0和i0相等,如果相等,说明那一块就是与其他不同的那一块。然后到关卡数组中取出字。可以从数组checkpoints看出,当遇到的是与其他不同的那一块时,就取下标为0的那一个字,否则就取下标为1的那一个字。所以,当是第一关时,我们要找的字就是“籍”,而其余的字是“藉"。

    接下来就处理字的位置,因为如果不处理,所有字都会堆在一起。处理位置的几行代码如下:

    tileText.x = j*50+7;
    tileText.y = i*50+7;
    

    接着我们来看看游戏时间的实现:

    function addTimeLine(){
        overLayer.graphics.drawRect(5,"dimgray",[500,0,20,500],true,"lightgray");
        overLayer.graphics.drawLine(15,"lightgray",[510,3,510,497]);
        overLayer.graphics.drawLine(15,"red",[510,3,510,497]);
        setTimeLine = setInterval(function(){drawTimeLine();},100);
    }
    function drawTimeLine(){
        nowLine = 3+((time/5)*495)/10;
        overLayer.graphics.drawLine(15,"lightgray",[510,3,510,497]);
        overLayer.graphics.drawLine(15,"red",[510,nowLine,510,497]);
        time++;
        if(time>50){
            clearInterval(setTimeLine);
            gameOver();
        }
    }
    

    我还是用graphics来实现的,只不过用的是里面的drawLine,参数是:

     

    第一个:线粗
    第二个:线颜色
    第三个:[起始坐标x,起始坐标y,结束坐标x,结束坐标y]

    实现减短时间条时,我先画一个颜色为lightgray的线使时间条清空一遍,再让画笔每100毫秒就移至3+((time/5)*495)/10,然后让这个坐标与510的位置连线。这样反复清屏加重绘,便实现了减短时间条。

    下一步,我们要实现鼠标事件,先看代码:

    function onDown(){
        var mouseX,mouseY;
        mouseX = event.offsetX;
        mouseY = event.offsetY;
    
        partX = Math.floor((mouseX)/50);
        partY = Math.floor((mouseY)/50);
        isTure(partX,partY);
    
        alert(partX+","+partY);
    }
    

    这一段代码很好理解,首先我取出鼠标位置,然后 将它除以50并取整,得出点的是哪一格,然后将点的那一格作为参数送进isTure,在里面我判断了参数值是否与i0和j0符合,如果符合,就调用处理胜利的函数。

    isTure的内容如下:

    function isTure(x,y){
        if(x==j0 && y==i0){
            clearInterval(setTimeLine);
            overLayer.graphics.drawRect(5,"dimgray",[(LGlobal.width - 420)*0.5,80,420,250],true,"lightgray");
            selectLayer.graphics.drawRect(5,"dimgray",[(LGlobal.width - 250)*0.5,230,250,50],true,"darkgray");
    
            for(var i=0;i<overTextContent.length;i++){
                overText = new LTextField();
                overText.weight = "bold";
                overText.color = "dimgray";
                overText.font = "黑体";
                if(i==0){
                    overText.text = overTextContent[i];
                    overText.size = 35;
                    overText.x = (LGlobal.width - overText.getWidth())*0.5;
                    overText.y = 120;
                    overLayer.addChild(overText);
                }else if(i==1){
                    if(checkpointNo == checkpoints.length-1){
                        overText.text = overTextContent[i+1];
                        overText.size = 20;
                        overText.x = (LGlobal.width - overText.getWidth())*0.5;
                        overText.y = 240; 
                        selectLayer.addChild(overText);
                        checkpointNo = 0;
                    }else{
                        overText.text = overTextContent[i];
                        overText.size = 20;
                        overText.x = (LGlobal.width - overText.getWidth())*0.5;
                        overText.y = 240;
                        selectLayer.addChild(overText);
                    }
                }
            }
        }
        tileLayer.removeEventListener(LMouseEvent.MOUSE_DOWN,onDown);
    }
    

    最后还有一些代码作为赢或输后的处理,很简单,我一笔带过:

    function gameOver(){
        overLayer.graphics.drawRect(5,"dimgray",[(LGlobal.width - 420)*0.5,80,420,250],true,"lightgray");
        gameoverLayer.graphics.drawRect(5,"dimgray",[(LGlobal.width - 250)*0.5,230,250,50],true,"darkgray");
    
        for(var i=0;i<gameoverTextContent.length;i++){
            gameoverText = new LTextField();
            gameoverText.weight = "bold";
            gameoverText.color = "dimgray";
            gameoverText.font = "黑体";
            if(i==0){
                gameoverText.text = gameoverTextContent[i];
                gameoverText.size = 35;
                gameoverText.x = (LGlobal.width - gameoverText.getWidth())*0.5;
                gameoverText.y = 120;
                gameoverLayer.addChild(gameoverText);
            }else if(i==1){
                gameoverText.text = gameoverTextContent[i];
                gameoverText.size = 20;
                gameoverText.x = (LGlobal.width - gameoverText.getWidth())*0.5;
                gameoverText.y = 240;
                gameoverLayer.addChild(gameoverText);
            }
        }
        tileLayer.removeEventListener(LMouseEvent.MOUSE_DOWN,onDown);
    }
    function gameReStart(){
        i0 = Math.floor(Math.random()*10);
        j0 = Math.floor(Math.random()*10);
    
        time = 0;
    
        tileLayer.removeAllChild();
        overLayer.removeAllChild();
        selectLayer.removeAllChild();
        backLayer.removeChild(selectLayer);
        backLayer.removeChild(overLayer);
        if(checkpointNo != checkpoints.length-1){
            checkpointNo++;
        }
        initTile();
        addEvent();
        addTimeLine();
    }
    function reTry(){
        i0 = Math.floor(Math.random()*10);
        j0 = Math.floor(Math.random()*10);
    
        time = 0;
    
        tileLayer.removeAllChild();
        overLayer.removeAllChild();
        gameoverLayer.removeAllChild();
        selectLayer.removeAllChild();
        backLayer.removeChild(selectLayer);
        backLayer.removeChild(overLayer);
        backLayer.removeChild(gameoverLayer);
    
        initTile();
        addEvent();
        addTimeLine();
    }
    

    基本思路

    首先我选取了一张大的原始图片,这张图片用来裁成一定数量的小方块(不用数学语言严谨描述了,影响阅读性),最好是选取的图片可以让每个小方块图片都有一定的辨识度。原图片右下角的一个小方块丢弃作为可移动的空白空间。每一个小方块都给她编上一个独一无二的号码。这个编号可以用来校验拼图是否完成。

    方块布局是使用UICollectionView来搭建的,难点在于拼图的移动,实际上我是把图块的移动处理成了图块位置的交换,只要点击你想要移动的图块,这个图块就会瞬移到空白位置,这样来说在游戏体验上移动更灵敏,效率更高!

    开玩时,将图块顺序打乱。

    三,下载和演示位置

     

    演示位置:

    下载位置:

    演示截图:

    威尼斯登录首页 2

    威尼斯登录首页 3

    核心算法

    判断当前点击的图块是否可移动

    -(void)calculateIndexOfMoveable {
    
        //记录空白块的索引,紧靠空白块的方块才可以移动,实际上就是与空白块交换位置。初始化时的空白块统一在右下角。
        //计算当前可移动的方块
        // 白色块所在行row = indexOfWhite / totalCols
        // 白色块所在列col = indexOfWhite % totalCols
        left = indexOfWhite - 1
        right = indexOfWhite + 1;
        up = indexOfWhite - totalCols;
        down = indexOfWhite + totalCols;
    
        //    但是要排除一些四周情况下的索引
        if ([self indexOfCol: left] > [self indexOfCol: indexOfWhite]) {
            //left 排除
            left = -1;
        }
        if ([self indexOfCol: right] < [self indexOfCol: indexOfWhite]) {
            //right 排除
            right = -1;
        }
        if (up < 0) {
            //up 排除
            up = -1;
        }
        if (down > totalCols*totalRows-1) {
            //down 排除
            down = -1;
        }
    }
    
    -(NSInteger)indexOfRow:(NSInteger)index {
        return index / totalCols;
    }
    
    -(NSInteger)indexOfCol:(NSInteger)index {
         return index % totalCols;
    }
    

    上面的 calculateIndexOfMoveable方法可以优化成如下四个方法:

    -(NSInteger)calculateIndexOfMoveable_left {
        left = indexOfWhite - 1;
        return [self indexOfCol: left] > [self indexOfCol: indexOfWhite] ? -1 : left;
    }
    
    -(NSInteger)calculateIndexOfMoveable_right {
        right = indexOfWhite + 1;
        return [self indexOfCol: right] < [self indexOfCol: indexOfWhite] ? -1 : right;
    }
    
    -(NSInteger)calculateIndexOfMoveable_up {
    
        return (indexOfWhite - totalCols) < 0 ? -1 : indexOfWhite - totalCols;
    }
    
    -(NSInteger)calculateIndexOfMoveable_down {
    
        return (indexOfWhite + totalCols) > (totalCols*totalRows-1) ? -1 : indexOfWhite + totalCols;
    }
    

    我这里定义了两个数组,一个是图片小方块的数组,一个是图片块对应的编号数组。这两个数组必须保持同步更新。也可以把图片小方块与其对应的编号作为一个模型类的属性。也可以建立一个字典,将编号与图片映射。
    初始化图片块数组:

    -(NSMutableArray *)dataSource {
        if (!_dataSource) {
            _dataSource = [NSMutableArray array];
    
            CGFloat x,y,w,h;
             w = (self.oringinalImg.image.size.width/totalCols)/[UIScreen mainScreen].scale;
             h = (self.oringinalImg.image.size.height/totalRows)/[UIScreen mainScreen].scale;
    
            for (int i=0; i<totalRows; i++) {
                for (int j=0; j<totalCols; j++) {
                    x = j*w;
                    y = i*h;
    
                    CGRect rect = CGRectMake(x,y,w,h);
                    if ((i==totalRows-1) && (j== totalCols-1)) {
                        [_dataSource addObject: [[UIImage alloc] init] ];
                    } else {
    
                        [_dataSource addObject: [self ct_imageFromImage:self.oringinalImg.image inRect: rect]];
                    }
                }
           }
         }
         return _dataSource;
    }
    

    初始化图片块对应的编号数组:

    -(NSMutableArray *)startIndexs {
        if (!_startIndexs) {
            _startIndexs = [NSMutableArray array];
            for (int i = 0; i < totalCols*totalRows; i++) {
                _startIndexs[i] = @(i);
            };
        }
        return _startIndexs;
    }
    

    裁剪图片的具体方法:

    /**
     *  从图片中按指定的位置大小截取图片的一部分
     *
     *  @param image UIImage image 原始的图片
     *  @param rect  CGRect rect 要截取的区域
     *
     *  @return UIImage
     */
    - (UIImage *)ct_imageFromImage:(UIImage *)image inRect:(CGRect)rect {
    
        //把像素rect 转化为点rect(如无转化则按原图像素取部分图片)
        CGFloat scale = [UIScreen mainScreen].scale;
        CGFloat x= rect.origin.x*scale,y=rect.origin.y*scale,w=rect.size.width*scale,h=rect.size.height*scale;
        CGRect dianRect = CGRectMake(x, y, w, h);
    
        //截取部分图片并生成新图片
        CGImageRef sourceImageRef = [image CGImage];
        CGImageRef newImageRef = CGImageCreateWithImageInRect(sourceImageRef, dianRect);
        UIImage *newImage = [UIImage imageWithCGImage:newImageRef scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp];
        return newImage;
    }
    

    两个数组同步随机乱序的方法,完成后两个数组在相同的索引位置其对应关系仍保持不变。

    - (void)randomArray {
        //两个数组同步打乱顺序,早知道这么麻烦我就用模型将索引值绑定image了。/(ㄒoㄒ)/~~
        NSMutableArray *newDatasourceArr = [NSMutableArray array];
        NSMutableArray *newStartIndexArr = [NSMutableArray array];
    
        int m = (int)self.dataSource.count;
    
        for (int i=0; i<m; i++) {
            int t = arc4random() % (self.dataSource.count);
            newDatasourceArr[i] = self.dataSource[t];
            newStartIndexArr[i] = self.startIndexs[t];
            self.dataSource[t] = [self.dataSource lastObject];
            self.startIndexs[t] = [self.startIndexs lastObject];
            [self.dataSource removeLastObject];
            [self.startIndexs removeLastObject];
        }
        self.dataSource = newDatasourceArr;
        self.startIndexs = newStartIndexArr;
    }
    


    12.17修改更新:关于打乱图序,我这种随机打乱顺序的做法欠妥,试玩几次后发现有些情况我总是还原不了,回忆上学时玩过的一款游戏没有出现过这样的情况。这时候我开始怀疑并不是所有的序列都可以进行还原。而我却忽略了,这非常不应该。

    打乱后还需要验证当前状态是否有解。根据相关定理,如果打乱后的排列与原始排列的逆序数奇偶性相同,则是可还原的(证明比较简单 参考链接——不可还原的拼图)。如果拼图的版块是随机打乱的,那么只有50%概率是可以被还原的。我这里统一将空格设置在末尾最后一个,可以忽略掉,不影响逆序数。

    方案二:让程序随机移动数次,这样肯定是能够还原的。这个“数次”也值得商榷,要尽可能乱,又不能太多次了。但是我这个游戏设定的打乱后空格统一在最后一格,还需要调整空格位置,同样用到刚才的逆序数相关定理,将空格与当前最后一个格子交换,现在排列奇偶性改变,还需要随机将非空格的两个格子进行交换一次。这样就可以了。

    方案三:对于m*n的拼图,从拼图板块中任取三块做轮换,通过[(m*n)/3]^2次轮换,即可实现相当“乱”的打乱效果。所谓三轮换,实质就是两次交换:如123,1与2交换后,这时候状态213,再3与2交换,这时候状态312。体现在拼图上很好实验,把包含空格的2*2格子进行各种移动变换,就对应了3轮换。


    还有个功能就是可以自定义几行几列,难点是需要动态更新相关数据,值得注意的是本例中cell是复用的,大小、内容需要根据需要即时调整。

    最后奉献上demo 欢迎大家找bug,并提出优化意见,谢谢!

    说明:本文首发于本人简书

    这是我的游戏记录,欢迎各位挑战:

    威尼斯登录首页 4

    接下来就来讲讲如何开发完成这款游戏的。(按“编年体”)

    准备阶段

    准备lufylegend游戏引擎,大家可以去官方网站下载:

    lufylegend.com/lufylegend

    引擎文档地址:

    lufylegend.com/lufylegend/api

    可以说,如果没有强大的lufylegend引擎,这种html5小游戏用原生canvas制作,少说要一天呢。

    0~30min

    准备素材(10min) + 修改素材(20min)。由于在下实在手残,不善于P图,修改图片用了大约20min,囧……

    30~50min

    开发开始界面。游戏不能没有开始界面所以我们首先实现这部分代码。在此之前是index.html里的代码,代码如下:

    <!DOCTYPE html>
    <html>
    <head>
        <title>Puzzle</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
        <script type="text/javascript" src="./lib/lufylegend-1.10.1.simple.min.js"></script>
        <script type="text/javascript" src="./js/Main.js"></script>
    </head>
    <body style="margin: 0px; font-size: 0px; background: #F2F2F2;">
        <div id="mygame"></div>
    </body>
    </html>
    

    主要是引入一些js文件,不多说。然后准备一个Main.js文件,在这个文件里添加初始化界面和加载资源的代码:

    /** 初始化游戏 */
    LInit(60, "mygame", 390, 580, main);
    
    var imgBmpd;
    /** 游戏层 */
    var stageLayer, gameLayer, overLayer;
    /** 拼图块列表 */
    var blockList;
    /** 是否游戏结束 */
    var isGameOver;
    /** 用时 */
    var startTime, time, timeTxt;
    /** 步数 */
    var steps, stepsTxt;
    
    function main () {
        /** 全屏设置 */
        if (LGlobal.mobile) {
            LGlobal.stageScale = LStageScaleMode.SHOW_ALL;
        }
        LGlobal.screen(LGlobal.FULL_SCREEN);
    
        /** 添加加载提示 */
        var loadingHint = new LTextField();
        loadingHint.text = "资源加载中……";
        loadingHint.size = 20;
        loadingHint.x = (LGlobal.width - loadingHint.getWidth()) / 2;
        loadingHint.y = (LGlobal.height - loadingHint.getHeight()) / 2;
        addChild(loadingHint);
    
        /** 加载图片 */
        LLoadManage.load(
            [
                {path : "./js/Block.js"},
                {name : "img", path : "./images/img.jpg"}
            ],
            null,
            function (result) {
                /** 移除加载提示 */
                loadingHint.remove();
    
                /** 保存位图数据,方便后续使用 */
                imgBmpd = new LBitmapData(result["img"]);
    
                gameInit();
            }
        );
    }
    
    function gameInit (e) {
        /** 初始化舞台层 */
        stageLayer = new LSprite();
        stageLayer.graphics.drawRect(0, "", [0, 0, LGlobal.width, LGlobal.height], true, "#EFEFEF");
        addChild(stageLayer);
    
        /** 初始化游戏层 */
        gameLayer = new LSprite();
        stageLayer.addChild(gameLayer);
    
        /** 初始化最上层 */
        overLayer = new LSprite();
        stageLayer.addChild(overLayer);
    
        /** 添加开始界面 */
        addBeginningUI();
    }
    

    以上代码有详细注释,大家可以对照引擎文档和注释进行阅读。有些全局变量会在以后的代码中使用,大家可以先忽略。接下来是addBeginningUI函数里的代码,用于实现开始界面:

    function addBeginningUI () {
        var beginningLayer = new LSprite();
        beginningLayer.graphics.drawRect(0, "", [0, 0, LGlobal.width, LGlobal.height], true, "#EDEDED");
        stageLayer.addChild(beginningLayer);
    
        /** 游戏标题 */
        var title = new LTextField();
        title.text = "拼图游戏";
        title.size = 50;
        title.weight = "bold";
        title.x = (LGlobal.width - title.getWidth()) / 2;
        title.y = 160;
        title.color = "#FFFFFF";
        title.lineWidth = 5;
        title.lineColor = "#000000";
        title.stroke = true;
        beginningLayer.addChild(title);
    
        /** 开始游戏提示 */
        var hint = new LTextField();
        hint.text = "- 点击屏幕开始游戏 -";
        hint.size = 25;
        hint.x = (LGlobal.width - hint.getWidth()) / 2;
        hint.y = 370;
        beginningLayer.addChild(hint);
    
        /** 开始游戏 */
        beginningLayer.addEventListener(LMouseEvent.MOUSE_UP, function () {
            beginningLayer.remove();
    
            startGame();
        });
    }
    

    到此,运行代码,得到我们的开始界面:

    威尼斯登录首页 5

    看到这个画面,其实我自己都想吐槽一下实在是太“朴素”了,囧……

    不过我这次图个制作速度,所以还望各位看官海量。

    50~90min

    这40分钟的时间,是最关键时期,期间我们要完成整个游戏的主体部分。首先,我们需要用代码来实现以下过程:

    初始化游戏界面数据(如游戏时间、所用步数)和显示一些UI部件(如图样)
    |
    -> 获取随机的拼图块位置
    |
    -> 显示打乱后的拼图块
    

    我们将这些步骤做成一个个的函数方便我们统一调用:

    function startGame () {
        isGameOver = false;
    
        /** 初始化时间和步数 */
        startTime = (new Date()).getTime();
        time = 0;
        steps = 0;
        /** 初始化拼图块列表 */
        initBlockList();
        /** 打乱拼图 */
        getRandomBlockList();
        /** 显示拼图 */
        showBlock();
        /** 显示缩略图 */
        showThumbnail();
        /** 显示时间 */
        addTimeTxt();
        /** 显示步数 */
        addStepsTxt();
    
        stageLayer.addEventListener(LEvent.ENTER_FRAME, onFrame);
    }
    

    函数一开始,我们把isGameOver变量设定为false代表游戏未结束,在后期的代码里,我们会看到这个变量的作用。接着我们初始化了用于表示时间和步数的timesteps这两个全局变量,另外初始化变量startTime的值用于后面计算游戏时间。
    接下来,我们就要开始初始化拼图块了。见initBlockList里的代码:

    function initBlockList () {
        blockList = new Array();
    
        for (var i = 0; i < 9; i++) {
            /** 根据序号计算拼图块图片显示位置 */
            var y = (i / 3) >>> 0, x = i % 3;
    
            blockList.push(new Block(i, x, y));
        }
    }
    

    这里我们使用了一个Block类,这个类用于显示拼图块和储存拼图块的数据,并提供了一些方法来操控拼图块,下面是其构造器的代码:

    function Block (index, x, y) {
        LExtends(this, LSprite, []);
    
        var bmpd = imgBmpd.clone();
        bmpd.setProperties(x * 130, y * 130, 130, 130);
        this.bmp = new LBitmap(bmpd);
        this.addChild(this.bmp);
    
        var border = new LShape();
        border.graphics.drawRect(3, "#CCCCCC", [0, 0, 130, 130]);
        this.addChild(border);
    
        this.index = index;
    
        this.addEventListener(LMouseEvent.MOUSE_UP, this.onClick);
    }
    

    Block类继承自LSprite,属于一个显示对象,所以我们在这个类中添加了一个位图对象用于显示拼图块对应的图片。除此之外,我们还为拼图块添加了一个边框,在显示时用于隔开周围的拼图块。Block类有一个index属性,代表拼图块在拼图块列表blockList中的正确位置。最后,我们为此类添加了一个鼠标按下事件,用于处理鼠标按下后移动图块操作。

    接下来我们还要介绍这个类的一个方法setLocation

    Block.prototype.setLocation = function (x, y) {
        this.locationX = x;
        this.locationY = y;
    
        this.x = x * 130;
        this.y = y * 130;
    };
    

    这个方法用于设置拼图块对象的显示位置以及保存拼图块的“数组位置”。什么是“数组位置”呢?各位看官可以通过下面的图片加以了解:

    威尼斯登录首页 6

    可以看到,“数组位置”就类似于二维数组中的元素下标。储存这个位置的作用在于可以很方便地从blockList中获取到附近的其他拼图块。这个方法在我们显示拼图时有调用到,在显示拼图之前,我们得先打乱拼图,见如下代码:

    function getRandomBlockList () {
        /** 随机打乱拼图 */
        blockList.sort(function () {
            return 0.5 - Math.random();
        });
    
        /** 计算逆序和 */
        var reverseAmount = 0;
    
        for (var i = 0, l = blockList.length, preBlock = null; i < l; i++) {
            if (!preBlock) {
                preBlock = blockList[0];
    
                continue;
            }
    
            var currentBlock = blockList[i];
    
            if (currentBlock.index < preBlock.index) {
                reverseAmount++;
            }
    
            preBlock = currentBlock;
        }
    
        /** 检测打乱后是否可还原 */
        if (reverseAmount % 2 != 0) {
            /** 不合格,重新打乱 */
            getRandomBlockList();
        }
    }
    

    打乱拼图部分直接用数组的sort方法进行随机打乱:

    blockList.sort(function () {
        return 0.5 - Math.random();
    });
    

    其实打乱算法有很多种,我这里采用最粗暴的方法,也就是随机打乱。这种算法简单是简单,坏在可能出现无法复原的现象。针对这个问题,就有配套的检测打乱后是否可还原的算法,具体的算法理论我摘用lufy大神的评论:

    此类游戏能否还原关键是看它打乱后的逆序次数之和是否为偶数
    假设你打乱后的数组中的每一个小图块为obj0obj1obj2,…它们打乱之前的序号分别为obj0.numobj1.num
    接下来循环数组,如果前者的序号比后者大,如obj0.num > obj1.num,这表示一个逆序
    当全部的逆序之和为奇数时表示不可还原,重新打乱即可,打乱后重新检测,直到逆序之和为偶数为止

    上面我给出的getRandomBlockList里的代码就是在实现打乱算法和检测是否可还原算法。

    还有一种打乱方式,大家可以尝试尝试:和复原拼图一样,将空白块一步一步地与周围的拼图随机交换顺序。这个打乱算法较上一种而言,不会出现无法复原的现象,而且可以根据打乱的步数设定游戏难度。

    在完成打乱拼图块后,如期而至的是显示拼图块:

    function showBlock() {
        for (var i = 0, l = blockList.length; i < l; i++) {
            var b = blockList[i];
    
            /** 根据序号计算拼图块位置 */
            var y = (i / 3) >>> 0, x = i % 3;
    
            b.setLocation(x, y);
    
            gameLayer.addChild(b);
        }
    }
    

    显示了拼图块后,我们要做的就是添加操作拼图块的功能。于是需要拓展Block类,为其添加事件监听器onClick方法:

    Block.prototype.onClick = function (e) {
        var self = e.currentTarget;
    
        if (isGameOver) {
            return;
        }
    
        var checkList = new Array();
    
        /** 判断右侧是否有方块 */
        if (self.locationX > 0) {
            checkList.push(Block.getBlock(self.locationX - 1, self.locationY));
        }
    
        /** 判断左侧是否有方块 */
        if (self.locationX < 2) {
            checkList.push(Block.getBlock(self.locationX + 1, self.locationY));
        }
    
        /** 判断上方是否有方块 */
        if (self.locationY > 0) {
            checkList.push(Block.getBlock(self.locationX, self.locationY - 1));
        }
    
        /** 判断下方是否有方块 */
        if (self.locationY < 2) {
            checkList.push(Block.getBlock(self.locationX, self.locationY + 1));
        }
    
        for (var i = 0, l = checkList.length; i < l; i++) {
            var checkO = checkList[i];
    
            /** 判断是否是空白拼图块 */
            if (checkO.index == 8) {
                steps++;
                updateStepsTxt();
    
                Block.exchangePosition(self, checkO);
    
                break;
            }
        }
    };
    

    首先,我们在这里看到了isGameOver全局变量的作用,即在游戏结束后,阻断点击拼图块后的操作。

    在点击了拼图块后,我们先获取该拼图块周围的拼图块,并将它们装入checkList,再遍历checkList,当判断到周围有空白拼图块后,即周围有index属性等于8的拼图块后,先更新操作步数,然后将这两个拼图块交换位置。具体交换拼图块位置的方法详见如下代码:

    Block.exchangePosition = function (b1, b2) {
        var b1x = b1.locationX, b1y = b1.locationY,
            b2x = b2.locationX, b2y = b2.locationY,
            b1Index = b1y * 3 + b1x,
            b2Index = b2y * 3 + b2x;
    
        /** 在地图块数组中交换两者位置 */
        blockList.splice(b1Index, 1, b2);
        blockList.splice(b2Index, 1, b1);
    
        /** 交换两者显示位置 */
        b1.setLocation(b2x, b2y);
        b2.setLocation(b1x, b1y);
    
        /** 判断游戏是否结束 */
        Block.isGameOver();
    };
    

    还有就是Block.getBlock静态方法,用于获取给定的“数组位置”下的拼图块:

    Block.getBlock = function (x, y) {
        return blockList[y * 3 + x];
    };
    

    Block.exchangePosition中,我们通过Block.isGameOver判断玩家是否已将拼图复原:

    Block.isGameOver = function () {
        var reductionAmount = 0, l = blockList.length;
    
        /** 计算还原度 */
        for (var i = 0; i < l; i++) {
            var b = blockList[i];
    
            if (b.index == i) {
                reductionAmount++;
            }
        }
    
        /** 计算是否完全还原 */
        if (reductionAmount == l) {
            /** 游戏结束 */
            gameOver();
        }   
    };
    

    到这里,我们就实现了打乱和操作拼图块部分。

    90~120min

    最后30min用于细枝末节上的处理,如显示拼图缩略图、显示&更新时间和步数,以及添加游戏结束画面,这些就交给如下冗长而简单的代码来完成吧:

    function showThumbnail() {
        var thumbnail = new LBitmap(imgBmpd);
        thumbnail.scaleX = 130 / imgBmpd.width;
        thumbnail.scaleY = 130 / imgBmpd.height;
        thumbnail.x = (LGlobal.width - 100) /2;
        thumbnail.y = 410;
        overLayer.addChild(thumbnail);
    }
    
    function addTimeTxt () {
        timeTxt = new LTextField();
        timeTxt.stroke = true;
        timeTxt.lineWidth = 3;
        timeTxt.lineColor = "#54D9EF";
        timeTxt.color = "#FFFFFF";
        timeTxt.size = 18;
        timeTxt.x = 20;
        timeTxt.y = 450;
        overLayer.addChild(timeTxt);
    
        updateTimeTxt();
    }
    
    function updateTimeTxt () {
        timeTxt.text = "时间:" + getTimeTxt(time);
    }
    
    function getTimeTxt () {
        var d = new Date(time);
    
        return d.getMinutes() + " : " + d.getSeconds();
    };
    
    function addStepsTxt () {
        stepsTxt = new LTextField();
        stepsTxt.stroke = true;
        stepsTxt.lineWidth = 3;
        stepsTxt.lineColor = "#54D9EF";
        stepsTxt.color = "#FFFFFF";
        stepsTxt.size = 18;
        stepsTxt.y = 450;
        overLayer.addChild(stepsTxt);
    
        updateStepsTxt();
    }
    
    function updateStepsTxt () {
        stepsTxt.text = "步数:" + steps;
    
        stepsTxt.x = LGlobal.width - stepsTxt.getWidth() - 20;
    }
    
    function onFrame () {
        if (isGameOver) {
            return;
        }
    
        /** 获取当前时间 */
        var currentTime = (new Date()).getTime();
    
        /** 计算使用的时间并更新时间显示 */
        time = currentTime - startTime;
        updateTimeTxt();
    }
    
    function gameOver () {
        isGameOver = true;
    
        var resultLayer = new LSprite();
        resultLayer.filters = [new LDropShadowFilter()];
        resultLayer.graphics.drawRoundRect(3, "#BBBBBB", [0, 0, 350, 350, 5], true,"#DDDDDD");
        resultLayer.x = (LGlobal.width - resultLayer.getWidth()) / 2;
        resultLayer.y = LGlobal.height / 2;
        resultLayer.alpha = 0;
        overLayer.addChild(resultLayer);
    
        var title = new LTextField();
        title.text = "游戏通关"
        title.weight = "bold";
        title.stroke = true;
        title.lineWidth = 3;
        title.lineColor = "#555555";
        title.size = 30;
        title.color = "#FFFFFF";
        title.x = (resultLayer.getWidth() - title.getWidth()) / 2;
        title.y = 30;
        resultLayer.addChild(title);
    
        var usedTimeTxt = new LTextField();
        usedTimeTxt.text = "游戏用时:" + getTimeTxt(time);
        usedTimeTxt.size = 20;
        usedTimeTxt.stroke = true;
        usedTimeTxt.lineWidth = 2;
        usedTimeTxt.lineColor = "#555555";
        usedTimeTxt.color = "#FFFFFF";
        usedTimeTxt.x = (resultLayer.getWidth() - usedTimeTxt.getWidth()) / 2;
        usedTimeTxt.y = 130;
        resultLayer.addChild(usedTimeTxt);
    
        var usedStepsTxt = new LTextField();
        usedStepsTxt.text = "所用步数:" + steps;
        usedStepsTxt.size = 20;
        usedStepsTxt.stroke = true;
        usedStepsTxt.lineWidth = 2;
        usedStepsTxt.lineColor = "#555555";
        usedStepsTxt.color = "#FFFFFF";
        usedStepsTxt.x = usedTimeTxt.x;
        usedStepsTxt.y = 180;
        resultLayer.addChild(usedStepsTxt);
    
        var hintTxt = new LTextField();
        hintTxt.text = "- 点击屏幕重新开始 -";
        hintTxt.size = 23;
        hintTxt.stroke = true;
        hintTxt.lineWidth = 2;
        hintTxt.lineColor = "#888888";
        hintTxt.color = "#FFFFFF";
        hintTxt.x = (resultLayer.getWidth() - hintTxt.getWidth()) / 2;
        hintTxt.y = 260;
        resultLayer.addChild(hintTxt);
    
        LTweenLite.to(resultLayer, 0.5, {
            alpha : 0.7,
            y : (LGlobal.height - resultLayer.getHeight()) / 2,
            onComplete : function () {
                /** 点击界面重新开始游戏 */
                stageLayer.addEventListener(LMouseEvent.MOUSE_UP, function () {
                    gameLayer.removeAllChild();
                    overLayer.removeAllChild();
    
                    stageLayer.removeAllEventListener();
    
                    startGame();
                });
            }
        });
    }
    

    Ok,2h下来,整个游戏就搞定咯~不得不表扬一下lufylegend这个游戏引擎,实在是可以大幅提升开发效率。

    源代码下载

    最后奉上源代码:点击下载

    本文由威尼斯手机平台登陆发布于前端资源,转载请注明出处:【威尼斯登录首页】做完这些后就可以开始游戏开发了,只要点击你想要移动的图块

    关键词:

上一篇:没有了

下一篇:没有了