2小时完成HTML5拼图小游戏

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

开言:

以前lufy前辈写过叫“ HTML5游戏开发-零基础开发RPG游戏”的系列文章,在那里面我学习了他的引擎以及了解了游戏脚本。自从看了那几篇文章,我便对游戏开发有了基本的认识。今天我也以零基础为视点,为大家讲述如何开发一款简单的游戏。希望大家看了这篇文章,能使你对理解游戏开发有帮助。

你可以先测试一下游戏:

 

图片 1

一,前言

本游戏是鄙人研究lufylegend数日之后,闲暇之余写下的。本游戏运用全新的技术HTML5写成的。游戏引擎为国产的lufylegend.js,大家可以去它的官网看看。游戏处于测试阶段,希望各位为游戏提点意见。另外游戏中的拼图是随即分配,保证游戏的随即性。

 

以下是游戏地址:

1,如何进行游戏开发

 

二,怎么玩?

下载地址(含原码+图片):

在线试玩:

http://www.lufylegend.com/lufylegend_developers/yorhom_puzzle/index.html

操作说明:

进入游戏后,通过点击拼图块,让图片还原,本游戏包含了游戏记录,大家可以给朋友互相比一比,看谁能在最短时间内让图片还原。

 

1.1游戏开发思想

本文依然要运用OOP思想(Object Oriented
Programming,面向对象编程),毕竟它很重要,很方便。

首先,为了让大家了解游戏开发的一些思想,我不妨说一下我对游戏开发的理解:

在游戏开发中,不是需要一个角色就要去立刻手动建设一个角色的。倘若是一个不断变化的游戏,用手慢慢改,那还不得累死。因此我们就需要用到循环或者时间轴事件。具体用循环还是时间轴事件,要看你是如何设计游戏。循环不要说,我们主要说说时间轴事件。

时间轴事件相当于一个循环,在它内部要执行的内容会不断地执行,也就是说是一个死循环。既然是死循环,那要改变游戏里的内容就会变得相当简单。只需要在外部更改界面上的属性,然后在时间轴事件内不断判断属性,执行变化即可。

三,游戏还需更新的地方

1,拼图打乱后,如果永远不能还原,不能重新打乱。

2,游戏记录存在缺陷。

3,加入玩家自己的图片。

 

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

1.2类的使用

 

另外还需要注意的是,游戏开发需要用到类。JavaScript定义类很简单,只用定义一个函数就行,属性用this来加。如下:

function people(){
    this.name = "yorhom";
    this.age = "13";
}

类的作用很大,加入有界面上有3个角色,我们只用循环3次,每循环一次就用局部变量来实例化一个角色类即可。

类操作里面比较重要一项的就是继承。比如说在lufylegend.js中,如果你的类继承自LSprite类,那就拥有show方法,到时候你在使用你的类时,只用更改属性,不用手动重绘就可以更新界面。除了方便以外,它还可以替你免去一些多余的代码。假如父类有个加鼠标事件的方法,而子类想要也,那就不需要在重新写一遍了,直接继承就行了。

四,游戏引擎

本游戏运用国产的lufylegend引擎,版本为1.5.2,如果大家感兴趣可以去官网看看:

上面有此引擎的下载和API介绍。它是一个非常适合做游戏的引擎,在这里不多说了。

 

图片 2

2,开始游戏开发

 

五,游戏截图

随机打乱的拼图块:

图片 3

简洁的开始界面:

图片 4

点击空缺处附近的一块可以使那一块向空缺处移动:

图片 5

var setScreenWidth = 390;
var setScreenHeight = 500;
init(50,"mylegend",setScreenWidth,setScreenHeight,main);
LSystem.screen(LStage.FULL_SCREEN);

var backLayer,loadingLayer,imgLayer,ctrlLayer,borderLayer,overLayer,initGameLayer,recordLayer,aboutLayer;
var steps = 0,time = 0;
var grades;
var isWin = 0;
var imglist = {};
var imgData = [
    {name:"garden",path:".garden.jpg"},
    {name:"outside",path:".outside.jpg"},
    {name:"button01",path:".utton01.png"},
    {name:"button02",path:".utton02.png"},
    {name:"button03",path:".utton03.png"},
    {name:"button04",path:".utton04.png"},
    {name:"button05",path:".utton05.png"},
    {name:"gameover_rect",path:".rect.png"},
    {name:"button01_over",path:".utton01_over.png"},
    {name:"button02_over",path:".utton02_over.png"},
    {name:"button04_over",path:".utton04_over.png"},
    {name:"button05_over",path:".utton05_over.png"}
];

function main(){
    loadingLayer = new LoadingSample1();
    addChild(loadingLayer);
    LLoadManage.load(
        imgData,
        function(progress){
            loadingLayer.setProgress(progress);
        },
        function(result){
            imglist = result;
            removeChild(loadingLayer);
            loadingLayer = null;
            //加入游戏开始时的背景
            gameInitBack();
            }
    );
}
var mapLookData;
function gameInit(){
    mapLookData = Math.ceil(Math.random()*(1-(-1))-1);

    //初始化游戏层
    initLayer();
    //初始化背景
    initBackground();
    //调用随即分配地图的函数
    getRandomMap();
    //加入两个用户可见的变量
    addText();
    //加入要达到的图片
    addTrueImg();
    //初始化拼图块
    initImg();
}
var startBtn;
var aboutBtn;
var recordBtn;
var initTextContent = ["智力大拼图","版本:v0.1"];
var initText;
function gameInitBack(){
    gameInit();
    initGameLayer = new LSprite();
    backLayer.addChild(initGameLayer);

    initGameLayer.graphics.drawRect(3,"dimgray",[15,30,360,440],true,"lightgray");

    for(var i=0;i<initTextContent.length;i++){
        initText = new LTextField();
        initText.weight = "bold";
        initText.text = initTextContent[i];
        if(i==0){
            initText.size = 25;
              initText.color = "dimgray";
            initText.font = "黑体";
            initText.x = (LGlobal.width - initText.getWidth())*0.5;
            initText.y = 130;
        }
        if(i==1){
            initText.size = 15;
              initText.color = "white";
            initText.font = "宋体";
            initText.x = (LGlobal.width - initText.getWidth())*0.5;
            initText.y = 170;    
        }
        initGameLayer.addChild(initText);
    }

    startBtn = new LButton(new LBitmap(new LBitmapData(imglist["button01"])),
        new LBitmap(new LBitmapData(imglist["button01_over"])));  
    startBtn.x = (LGlobal.width - startBtn.getWidth())*0.5;
    startBtn.y = 200;  
    initGameLayer.addChild(startBtn);

    aboutBtn = new LButton(new LBitmap(new LBitmapData(imglist["button05"])),
        new LBitmap(new LBitmapData(imglist["button05_over"])));  
     aboutBtn.x = (LGlobal.width - aboutBtn.getWidth())*0.5;  
        aboutBtn.y = 250;  
        initGameLayer.addChild(aboutBtn);


    recordBtn = new LButton(new LBitmap(new LBitmapData(imglist["button02"])),
        new LBitmap(new LBitmapData(imglist["button02_over"])));  
     recordBtn.x = (LGlobal.width - recordBtn.getWidth())*0.5;  
        recordBtn.y = 300;  
        initGameLayer.addChild(recordBtn);

        startBtn.addEventListener(LMouseEvent.MOUSE_DOWN, function(){
        //加入事件监听
        addEvent();

        if(startStatus == 0){
            startStatus++;
            changeTime = setInterval(function(){time+=1; timeText.text = "游戏用时:"+time;},1000);
        }
        LTweenLite.to(initGameLayer,1,{alpha:1});
        initGameLayer.removeAllChild();
        backLayer.removeChild(initGameLayer);
    });  
    recordBtn.addEventListener(LMouseEvent.MOUSE_DOWN, function(){
        showRecord();
    });
    aboutBtn.addEventListener(LMouseEvent.MOUSE_DOWN, aboutGame);

    LTweenLite.to(initGameLayer,1,{alpha:0.9});
}
var initCloseBtn;
var aboutTextArr = ["关于游戏","游戏引擎:lufylegend","游戏版本:v0.1","程序设计:Yorhom","素材来源:按钮自画,拼图来自网络","我的博客:blog.csdn.net/yorhomwang","我的邮箱:wangyuehao1999@gmail.com"];
var aboutText;

function aboutGame(){
    aboutLayer = new LSprite();
    backLayer.addChild(aboutLayer);

    aboutLayer.graphics.drawRect(3,"dimgray",[15,30,360,440],true,"lightgray");

    initCloseBtn = new LButton(new LBitmap(new LBitmapData(imglist["button03"])),
        new LBitmap(new LBitmapData(imglist["button03"])));  
    initCloseBtn.x = 342;
    initCloseBtn.y = 33;  
    aboutLayer.addChild(initCloseBtn);
    initCloseBtn.addEventListener(LMouseEvent.MOUSE_DOWN, function(event){
        LTweenLite.to(aboutLayer,1,{alpha:1});
        aboutLayer.removeAllChild();
        backLayer.removeChild(aboutLayer);
    });
    LTweenLite.to(aboutLayer,1,{alpha:0.9});

    for(var i=0;i<aboutTextArr.length;i++){
        aboutText = new LTextField();
        aboutText.weight = "bold";
        aboutText.text = aboutTextArr[i];
        if(i==0){
            aboutText.size = 25;
              aboutText.color = "dimgray";
            aboutText.font = "黑体";
            aboutText.x = (LGlobal.width - aboutText.getWidth())*0.5;
            aboutText.y = 33;
        }else{
            aboutText.size = 12;
              aboutText.color = "white";
            aboutText.font = "Tahoma";
            aboutText.x = 30;
            aboutText.y = 100 + (i-1)*22;    
        }
        aboutLayer.addChild(aboutText);
    }    
}
function initLayer(){
    backLayer = new LSprite();
    addChild(backLayer);

    imgLayer = new LSprite();
    backLayer.addChild(imgLayer);

    borderLayer = new LSprite();
    backLayer.addChild(borderLayer);

    overLayer = new LSprite();
    backLayer.addChild(overLayer);

    ctrlLayer = new LSprite();
    backLayer.addChild(ctrlLayer);
}
function initBackground(){
    //画出图片上的横向网格
    var color = "gray";
    for(var i=0;i<3;i++){
        borderLayer.graphics.drawRect(5,color,[0,i*130,setScreenWidth,130],false);
    }
    //画出图片上的纵向网格
    for(var i=0;i<3;i++){
        borderLayer.graphics.drawRect(5,color,[i*130,0,130,390],false);
    }
    //画出游戏的边框和背景
    borderLayer.graphics.drawRect(6,"dimgray",[0,0,setScreenWidth,setScreenHeight],false);
    backLayer.graphics.drawRect(5,"dimgray",[0,0,setScreenWidth,setScreenHeight],true,"lightgray");
}
//地图数组
var tileData = [
    [0,1,2],
    [3,4,5],
    [6,8,7]
];

function initImg(){
    var i,j;
    var index,indexY,indexX;
    var bitmapdata,bitmap;

    for(i=0;i<3;i++){
        for(j=0;j<3;j++){
            //从地图数组中得到相应位置的图片坐标
            index = tileData[i][j];
            //小图片的竖坐标
            indexY = Math.floor(index/3);
             //小图片的横坐标
            indexX = index - indexY*3;

            //得到小图片
            if(mapLookData==0){
                bitmapdata = new LBitmapData(imglist["garden"],indexX*130,indexY*130,130,130);
            }else{
                bitmapdata = new LBitmapData(imglist["outside"],indexX*130,indexY*130,130,130);
            }
            //bitmapdata = new LBitmapData(imglist["garden"],indexX*130,indexY*130,130,130);
            bitmap = new LBitmap(bitmapdata);
            //设置小图片的显示位置
            bitmap.x = j*130;  
            bitmap.y = i*130;
            //将小图片显示到地图层
            imgLayer.addChild(bitmap);
            }
    }
    toWin();
    stepText.text = "行动次数:"+steps;
}
//原数组
var tileOriginaArray = [];
//最终数组
var finallyMapArry = [];

function getRandomMap(){
    //用来装每一行的值的临时数组
    var partTimeArray = [];
    //用于遍历变量
    var i,j;
    //设置最大限度
    var count = 9;

    //第一次遍历让原数组获得值
    for(i=0;i<count;i++){
        tileOriginaArray[i] = i;
    }
    //将原数组打乱顺序
    tileOriginaArray.sort(function(){return 0.5 - Math.random();});
    //第二次遍历让原数组分割成二维数组
    for(j=0;j<count;j++){
        //给二维数组某一行赋值
        partTimeArray[partTimeArray.length] = tileOriginaArray[j];
        //判断是否达到每行需要的列数
        if((j+1)%3==0){
            //给最终数组装入每一行
            finallyMapArry.push(partTimeArray);
            //清空临时数组
            partTimeArray = [];
        }
    }
    //给地图数组赋值为最终数组
    tileData = finallyMapArry;
}
function addTrueImg(){
    var trueBitmapdata,trueBitmap;

    if(mapLookData==0){
        trueBitmapdata = new LBitmapData(imglist["garden"]);
    }else{
        trueBitmapdata = new LBitmapData(imglist["outside"]);
    }
    trueBitmap = new LBitmap(trueBitmapdata);
    trueBitmap.x = 160;
    trueBitmap.y = 400;
    trueBitmap.scaleX = 0.2;
    trueBitmap.scaleY = 0.2;
    overLayer.addChild(trueBitmap);

    overLayer.graphics.drawRect(3,"dimgray",[15,423,120,28],false);
    overLayer.graphics.drawRect(3,"dimgray",[255,423,120,28],false);
}
function addEvent(event){
    imgLayer.addEventListener(LMouseEvent.MOUSE_DOWN,onDown);
}
var partX,partY;
var changeTime;
var startStatus = 0;

function onDown(event){
    var mouseX,mouseY;
    mouseX = event.offsetX;
    mouseY = event.offsetY;

    partX = Math.floor(mouseX/130);
    partY = Math.floor(mouseY/130);
    queryMove(partX,partY);
}
function queryMove(x,y){
    steps+=1;

    if(x<2 && tileData[y][x+1] == 8){
        tileData[y][x+1] = tileData[y][x];
        tileData[y][x] = 8;
        initImg();
    }else if(x>0 && tileData[y][x-1] == 8){
        tileData[y][x-1] = tileData[y][x];
        tileData[y][x] = 8;
        initImg();
    }else if(y<2 && tileData[y+1][x] == 8){
        tileData[y+1][x] = tileData[y][x];
        tileData[y][x] = 8;
        initImg();
    }else if(y>0 && tileData[y-1][x] == 8){
        tileData[y-1][x] = tileData[y][x];
        tileData[y][x] = 8;
        initImg();
    }
}
//正确数组
var trueTileData = [
    [0,1,2],
    [3,4,5],
    [6,7,8]
];
var amount;
localStorage["locRecordNo"] = 0;

function toWin(){
    if(isWin != 1 && tileData.toString() == trueTileData.toString()){
        amount = steps + time;
        gameOver();
        writeRecord();
    }
}
//定义输出层
var stepText;
var timeText;

function addText(){    
    stepText = new LTextField();
    stepText.size = 10;
      stepText.color = "black";
    stepText.x = 20;
    stepText.y = 430;
    overLayer.addChild(stepText);

    timeText = new LTextField();
    timeText.size = 10;
      timeText.color = "black";
    timeText.text = "游戏用时:"+time;
    timeText.x = 260;
    timeText.y = 430;
    overLayer.addChild(timeText);
}
var btn01;
var btn02;
var gameoverBitMap;
var winText;
var winTextContent = [];

function gameOver(){
    if(amount<40){
        grades = "圣者";
    }else if(amount>39 && amount<80){
        grades = "前将军";
    }else if(amount>79 && amount<160){
        grades = "左将军";
    }else if(amount>159 && amount<200){
        grades = "右将军";
    }else if(amount>199 && amount<240){
        grades = "中将军";
    }else if(amount>239 && amount<300){
        grades = "后将军";
    }else if(amount>299 && amount<500){
        grades = "帐中军师";
    }else if(amount>499 && amount<1000){
        grades = "小小策士";
    }else if(amount>999 && amount<2000){
        grades = "献世小卒";
    }else{
        grades = "押粮步兵";
    }
    winTextContent = ["恭喜您,您通关了","您的等级是:"+grades];

    gameoverBitMap = new LBitmap(new LBitmapData(imglist["gameover_rect"]));
    gameoverBitMap.x = 30;
    gameoverBitMap.y = 100;
    ctrlLayer.addChild(gameoverBitMap);

    imgLayer.removeEventListener(LMouseEvent.MOUSE_DOWN,onDown);
    clearInterval(changeTime);
    startStatus = -1;
    isWin = 1;

    for(var i=0;i<winTextContent.length;i++){
        winText = new LTextField();
        winText.weight = "bold";
        winText.text = winTextContent[i];
        if(i==0){
            winText.size = 25;
              winText.color = "dimgray";
            winText.font = "黑体";
            winText.x = (LGlobal.width - winText.getWidth())*0.5;
            winText.y = 130;
        }
        if(i==1){
            winText.size = 15;
              winText.color = "white";
            winText.font = "宋体";
            winText.x = (LGlobal.width - winText.getWidth())*0.5;
            winText.y = 170;    
        }
        ctrlLayer.addChild(winText);
    }

    btn01 = new LButton(new LBitmap(new LBitmapData(imglist["button01"])),
        new LBitmap(new LBitmapData(imglist["button01_over"])));  
    btn01.x = (LGlobal.width - btn01.getWidth())*0.5;
    btn01.y = 200;  
    ctrlLayer.addChild(btn01);  

    btn02 = new LButton(new LBitmap(new LBitmapData(imglist["button02"])),
        new LBitmap(new LBitmapData(imglist["button02_over"])));  
     btn02.x = (LGlobal.width - btn02.getWidth())*0.5;  
        btn02.y = 250;  
        ctrlLayer.addChild(btn02);

        btn01.addEventListener(LMouseEvent.MOUSE_DOWN, reStart);  
    btn02.addEventListener(LMouseEvent.MOUSE_DOWN, showRecord);

    LTweenLite.to(ctrlLayer,1,{alpha:0.9});
}
function reStart(event){
    backLayer.removeAllChild();
    removeChild(backLayer);
    gameInit();
    addEvent();
    stepText.text = "行动次数:0";
    steps = 0;
    time = 0;
    startStatus = 0;    

    if(startStatus == 0){
        startStatus++;
        changeTime = setInterval(function(){time+=1; timeText.text = "游戏用时:"+time;},1000);
    }
    LTweenLite.to(ctrlLayer,1,{alpha:1});
    ctrlLayer.removeAllChild();
    isWin = 0;
}
var recordRunTimesArry;
var recordTimeArry;
var maxTimeArr = [];
var maxRunTimesArr = [];

function writeRecord(){
    if(localStorage["locRecordRunTimesArry"] == undefined || localStorage["locRecordTimeArry"] == undefined){
        localStorage.setItem("locRecordRunTimesArry",'');
        localStorage.setItem("locRecordTimeArry",'');

        recordRunTimesArry = localStorage["locRecordRunTimesArry"].split(",");
        recordTimeArry = localStorage["locRecordTimeArry"].split(",");

        recordRunTimesArry[recordRunTimesArry.length] = steps;
        localStorage["locRecordRunTimesArry"] = recordRunTimesArry;

        recordTimeArry[recordTimeArry.length] = time;
        localStorage["locRecordTimeArry"] = recordTimeArry;
    }else{
        recordRunTimesArry = localStorage["locRecordRunTimesArry"].split(",");
        recordTimeArry = localStorage["locRecordTimeArry"].split(",");

        recordRunTimesArry[recordRunTimesArry.length] = steps;
        localStorage["locRecordRunTimesArry"] = recordRunTimesArry;

        recordTimeArry[recordTimeArry.length] = time;
        localStorage["locRecordTimeArry"] = recordTimeArry;
    }
}
var btn03;
var btn04;
function showRecord(){
    if(localStorage["locRecordRunTimesArry"] == undefined || localStorage["locRecordTimeArry"] == undefined){
        localStorage.setItem("locRecordRunTimesArry",'');
        localStorage.setItem("locRecordTimeArry",'');

        recordRunTimesArry = localStorage["locRecordRunTimesArry"].split(",");
        recordTimeArry = localStorage["locRecordTimeArry"].split(",");
    }else{
        recordRunTimesArry = localStorage["locRecordRunTimesArry"].split(",");
        recordTimeArry = localStorage["locRecordTimeArry"].split(",");
    }
    recordLayer = new LSprite();
    backLayer.addChild(recordLayer);

    getRecord();
    printRecordFont();
    recordLayer.graphics.drawRect(3,"dimgray",[15,30,360,440],true,"lightgray");
    LTweenLite.to(recordLayer,1,{alpha:0.9});

    btn03 = new LButton(new LBitmap(new LBitmapData(imglist["button03"])),
        new LBitmap(new LBitmapData(imglist["button03"])));  
    btn03.x = 342;
    btn03.y = 33;  
    recordLayer.addChild(btn03);

    btn04 = new LButton(new LBitmap(new LBitmapData(imglist["button04"])),
        new LBitmap(new LBitmapData(imglist["button04_over"])));  
    btn04.x = (LGlobal.width - btn04.getWidth())*0.5;;
    btn04.y = 420;  
    recordLayer.addChild(btn04);

    btn03.addEventListener(LMouseEvent.MOUSE_DOWN, 
    function(event){
        recordLayer.removeAllChild();
        backLayer.removeChild(recordLayer);
        LTweenLite.to(backLayer,1,{alpha:1});
    });
    btn04.addEventListener(LMouseEvent.MOUSE_DOWN, 
    function(event){
        localStorage.removeItem("locRecordRunTimesArry")
        localStorage.removeItem("locRecordTimeArry");
    });
}
var recordFontArr = ["游戏记录","名次","行动次数","游戏用时"];
var recordText;
var recordFontX = 120;
var recordFontY = 30;

function printRecordFont(){
    for(var i=0;i<recordFontArr.length;i++){
        recordText = new LTextField();
        recordText.weight = "bold";
        recordText.text = recordFontArr[i];
        if(i==0){
            recordText.size = 25;
              recordText.color = "dimgray";
            recordText.font = "黑体";
            recordText.x = (LGlobal.width - recordText.getWidth())*0.5;
            recordText.y = 33;
        }else if(i>=1 && i<=4){
            recordText.size = 12;
              recordText.color = "gray";
            recordText.font = "宋体";
            recordText.x = 25 + (i-1)*recordFontX;
            recordText.y = 80;    
        }
        recordLayer.addChild(recordText);
    }
    for(var i=0;i<10;i++){
        recordText = new LTextField();
        recordText.weight = "bold";
        recordText.font = "宋体";
        recordText.color = "white";
        recordText.size = 12;
        recordText.x = 25;
        recordText.y = 140 + (i-1)*recordFontY;
        recordText.text = i + 1;
        recordLayer.addChild(recordText);
    }
    for(var i=0;i<10;i++){
        recordText = new LTextField();
        recordText.weight = "bold";
        recordText.font = "宋体";
        recordText.color = "white";
        recordText.size = 12;
        recordText.x = 25 + 1*recordFontX;
        recordText.y = 140 + (i-1)*recordFontY;;
        recordText.text = maxRunTimesArr[i+1] || '';
        recordLayer.addChild(recordText);
    }
    for(var i=0;i<10;i++){
        recordText = new LTextField();
        recordText.weight = "bold";
        recordText.font = "宋体";
        recordText.color = "white";
        recordText.size = 12;
        recordText.x = 25 + 2*recordFontX;
        recordText.y = 140 + (i-1)*recordFontY;;
        recordText.text = maxTimeArr[i+1] || '';
        recordLayer.addChild(recordText);
    }
}
function getRecord(){
    maxTimeArr = recordTimeArry.sort(function(a,b){return a-b;});
    maxRunTimesArr = recordRunTimesArry.sort(function(a,b){return a-b;});
}

 

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

2.1开发准备

由于本次开发用到了lufylegend.js开源引擎,所以首先需要下载它。

下载地址:

API 文档:

另外,由于是html5游戏,所以你需要一个支持html5的浏览器。当然,如果你已经有了这样的浏览器,那就直接开始吧。

准备阶段

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

lufylegend.com/lufylegend

引擎文档地址:

lufylegend.com/lufylegend/api

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

2.2开始编程

首先来看一下Main.js。首先定义一些层变量:

var backLayer,
loadingLayer,
logoLayer,
sceneLayer,
snowLayer,
stageLayer,
charaLayer,
overLayer,
gameoverLayer;

另外一些闲杂变量:

var point = 0,time = 1000*30;
var showTime;
var plopSound,backSound;
var playerName;
var pointText,timeText,resultText;

还有几个散乱的家伙藏在角落里,也被我给找到贴出来:

var snowingSpeed = 0;
var snowingSpeedIndex = 20;
var snowChildList = [];
var canSnowing = true;
var showChara = false;

接下来初始化引擎

init(50,"mylegend",600,400,main);
LSystem.screen(LStage.FULL_SCREEN);

init是引擎初始化函数,用法如下:

 


 

init( 
    speed, 
    divid, 
    width, 
    height, 
    completeFunc, 
    type 
)

 

 

 

 

■作用:

 

库件初始化

 

■参数:

speed:游戏速度设定

divid:传入一个div的id,库件进行初始化的时候,会自动将canvas加入到此div内部

width:游戏界面宽

height:游戏界面高

completeFunc:游戏初始化后,调用此函数

type:当为null时,会先进行页面的onload操作,如果你的init函数调用是在onload之后,那么需要将此参数设为LEvent.INIT


 

LSystem.screen是一调整屏幕大小的方法。如果参数写LStage.FULL_SCREEN说明调整为全屏。

接下来是加载图片:

var imglist = [];
var imgData = [
    {path:"./js/gameLogo.js",type:"js"},
    {path:"./js/Charactor.js",type:"js"},
    {path:"./js/Stage.js",type:"js"},
    {name:"player",path:".irplane.png"},
    {name:"logoback",path:".logoback.jpg"},
    {name:"background",path:".kground.png"},
    {name:"house",path:".house.png"},
    {name:"costume0",path:".ostume0.png"},
    {name:"costume1",path:".ostume1.png"},
    {name:"costume2",path:".ostume2.png"},
    {name:"costume3",path:".ostume3.png"},
    {name:"costume4",path:".ostume4.png"},
    {name:"costume5",path:".ostume5.png"},
    {name:"costume6",path:".ostume6.png"},
    {name:"costume7",path:".ostume7.png"}
];

上面是加载图片列表,以下是加载时用的代码:

//开始加载图片
LLoadManage.load(
    imgData,
    function(progress){
        //绘制进度条
        loadingLayer.setProgress(progress);
    },
    function(result){
        imglist = result;
        removeChild(loadingLayer);
        loadingLayer = null;
        //初始化游戏
        gameInit();
        //加入开始界面
        addLogo();
    }
);

将以上代码写到main函数中,就可以实现加载图片了。LLoadManage类是lufylegend中一个加载图片的类,用它可以方便地加载图片。用法如下:

 


 

load($list,$onupdate,$oncomplete)

 

■作用:

 

读取文件组

 

■参数:

$list:文件数组

$onupdate:读取中调用函数,一般用来显示游戏进度

$oncomplete:全部文件读取完成后调用函数

 

■详细说明:

这个函数可以接收一个数组,然后加载数组里的所有文件


这样做的好处是可以在使用图片时更方便,只用写path对应的name上去就行了。

整个main函数代码如下:

function main(){
    //初始化加载层
    loadingLayer = new LoadingSample3();
    addChild(loadingLayer);
    //开始加载图片
    LLoadManage.load(
        imgData,
        function(progress){
            //绘制进度条
            loadingLayer.setProgress(progress);
        },
        function(result){
            imglist = result;
            removeChild(loadingLayer);
            loadingLayer = null;
            //初始化游戏
            gameInit();
            //加入开始界面
            addLogo();
        }
    );
    //加载声效音乐
    plopSound = new LSound();
    var plopUrl = "./sounds/plop.mp3";
    plopSound.load(plopUrl);
    //加载背景音乐
    backSound = new LSound();
    var backsoundUrl = "./sounds/back_music.mp3";
    backSound.load(backsoundUrl);
}

在上面的代码中,我用LSound类加了背景音乐,这样一来顺便试一下新功能。看看gameInit里代码:

function gameInit(){
    //初始化层
    initLayer();
    //加入时间轴事件
    backLayer.addEventListener(LEvent.ENTER_FRAME,onframe);
    //加入鼠标事件
    backLayer.addEventListener(LMouseEvent.MOUSE_DOWN,onmousedown);
}

initLayer和onmousedown中的代码:

function onmousedown(event){
    //播放声效音乐
    plopSound.play();
    if(showChara == true && stageLayer.childList.length < 6){
        //加入障碍物
        addStage();
    }
}
function initLayer(){
    //加入底板层
    backLayer = new LSprite();
    addChild(backLayer);
    //加入图标层
    logoLayer = new LSprite();
    backLayer.addChild(logoLayer);
    //加入雪花层
    snowLayer = new LSprite();
    backLayer.addChild(snowLayer);
    //加入场景层
    sceneLayer = new LSprite();
    backLayer.addChild(sceneLayer);
    //加入礼物层
    stageLayer = new LSprite();
    backLayer.addChild(stageLayer);
    //加入人物层
    charaLayer = new LSprite();
    backLayer.addChild(charaLayer);
    //加入输出层
    overLayer = new LSprite();
    backLayer.addChild(overLayer);
    //加入游戏结束层
    gameoverLayer = new LSprite();
    backLayer.addChild(gameoverLayer);
}

以上代码都添加了注释,很容易看懂。有几个引擎中的类的用法提一下,LSprite用法:

 


 

LSprite()

 

 

■作用:

 

LSprite
类是基本显示列表构造块,一个可显示图形并且也可包含子项的显示列表节点。

 

■可用属性:

type:类型

x:坐标x

y:坐标y

scaleX:X坐标方向上的缩放比例

scaleY:Y坐标方向上的缩放比例

alpha:透明度

rotate:旋转角度

visible:是否可见,当设为false的时候,该LBitmap对象不可视,且内部所有处理都将停止

childList:该对象的所有子项

graphics:指定属于此 LSprite 的 LGraphics 对象,在此 LSprite
中可执行矢量绘图命令。

box2dBody:结合box2dweb后,创建的body2d

mask:遮罩

filters:光晕效果,具体可参照LDropShadowFilter类的介绍


addEventListener的用法:


 

addEventListener(type,listener)

 

 

■作用:

 

注册事件侦听器对象,以使侦听器能够接收事件通知。

 

■参数:

type:事件的类型。

listener:处理事件的侦听器函数。


在加载函数中,我调用了addLogo,它是用来显示开场界面的。由于游戏本身很简单,所以要加一个很绚丽的开场界面。

addLogo的代码如下:

var logoText;
var startBtn;
function addLogo(){
    //加入背景
    var bitmapData = new LBitmapData(imglist["logoback"],0,0,1024,768);
    var bitmap = new LBitmap(bitmapData);
    bitmap.scaleX = 0.6;
    bitmap.scaleY = 0.6;
    logoLayer.addChild(bitmap);
    //加入文字
    addLogoText();
}

在其中我给背景添上图片,用到了LBitmapData和LBitmap。用法很多,大家可以自己去API里看看。这里就先不多说了。

 

addLogoText里的代码:

function addLogoText(){
    //大标题
    logoText = new LTextField();
    logoText.size = 50;
    logoText.color = "white";
    logoText.font = "HG行書体";
    logoText.text = "Christmas";
    logoText.stroke = true;
    logoText.lineWidth = 2;
    logoText.x = 50;
    logoText.y = 20;
    logoLayer.addChild(logoText);
    //加入滤镜效果
    var titleShadow = new LDropShadowFilter(5,45,"red");
    for(var i=0;i<2;i++){
        logoText.filters = [titleShadow];
        logoLayer.addChild(logoText);
    }
    //开始指示
    logoText = new LTextField();
    logoText.size = 30;
    logoText.color = "white";
    logoText.font = "HG行書体";
    logoText.text = "Tap to Start Game";
    logoText.x = 150;
    logoText.y = 190;
    logoLayer.addChild(logoText);
    //加入开始游戏事件
    logoLayer.addEventListener(LMouseEvent.MOUSE_UP,startGame);
    //加入滤镜效果
    var shadow = new LDropShadowFilter(5,45,"black",0);
    logoText.filters = [shadow];
}

界面运行出来后,得到了一个静态的结果,游戏嘛就得富有动态,于是我做了一个下雪效果。它在onframe函数中,也就是我们说的时间轴事件中:

if(canSnowing == true){
    //加入雪花
    addSnow();
}

接着看addSnow函数:

function addSnow(){
    snowLayer.graphics.clear();
    var snowx = Math.random()*(LStage.width-10)+10;
    var n = snowChildList.length;
    while(n--){
        var s = snowChildList[n];
        s.y += s.s;
        snowLayer.graphics.drawArc(2,"white",[s.x,s.y,2,0,2*Math.PI],true,"white");
    }
    snowChildList.push({x:snowx,y:0,s:10});
}

它实现的方法在上一篇文章中提到过,可以看看,这里就不多讲了:

 

如何制作一款HTML5 RPG游戏引擎——第二篇,烟雨+飞雪效果

运行代码得到一个相当酷的界面,大家可以看一下:

图片 6
光有界面也不能叫游戏,接下来就是游戏主体部分。

我们先前提到过类,现在就来用类实战一下。首先来看charactor人物类:

function Charactor(data){
    base(this,LSprite,[]);
    //设定x和y坐标
    this.x = 0;
    this.y = 0;
    //设定模式
    this.mode = "right";
    this.speed = 5;
    //加入图片
    this.data = data;
    var list = LGlobal.divideCoordinate(227,158,1,1);
    var bitmapdata = new LBitmapData(imglist[this.data]);
    //加入动画
    this.anima = new LAnimation(this,bitmapdata,list);
    this.anima.setAction(0,1,0,false);
}

其中有一个LAnimation方法,它是lufylegend中播放动画的类,使用说明如下:

 


 

LAnimation(layer,data,list)

 

 

■作用:

 

实现简单动画的播放,原理是将一张大的图片,按照保存有坐标的二维数组保存的坐标来逐个显示。

 

■参数:

layer:LSprite显示层

data:LBitmapData对象

list:一个存有坐标的2维数组

 

■详细说明:

LAnimation类实现简单动画的播放,用于制作人物行走等效果非常方便

 

■可用属性:

layer:动画显示时,LAnimation的父级层

data:LBitmapData对象

list:坐标数组。


其他的就很容易懂了,Charactor类有个move方法,用于人物移动,如下:

Charactor.prototype.move = function(){
    //当向右飞行时
    if(this.mode == "right" && this.x < LStage.width-149){
        this.anima.setAction(0,1,0,false);
        this.x += this.speed;
    }else{
        this.mode = "left";
    }
    //当向左飞行时
    if(this.mode == "left" && this.x > 0){
        this.anima.setAction(0,1,0,true);
        this.x -= this.speed;
    }else{
        this.mode = "right";
    }
}

这段代码可以使人物移动,将这段代码放在onframe中就可以实现让人物来回移动了。逻辑很简单,大家可以看看。

 

接着就是实例化人物了。代码如下:

function addChara(){
    oldMan = new Charactor("player");
    showChara = true;
    charaLayer.addChild(oldMan);
}

接着是onframe中的代码:

if(showChara == true){
    //使人物动起来
    oldMan.move();
    //改变时间显示
    timeText.text = "Time:" + showTime;
    if(time>0){
        time -= 30000/(30000/50);
    }else{
        playerName = getName();
        gameOver();
    }
}

在这里我门判断时间是否为0,如果为0就游戏结束。当然,这是后话,这里只提一下。

 

接下来看Stage类,这个很重要,大家一定要认真看哦!

var stageSpeed = 5;
function Stage(){
    base(this,LSprite,[]);
    //取出一个整数,使0<=index<=7成立
    var index = Math.floor(Math.random()*7);
    //将index的值取出对应的图片
    var bitmap = new LBitmap(new LBitmapData(imglist["costume"+index]));
    //定义礼物的模式
    this.mode = "";
    this.addChild(bitmap);
}

这是Stage类构造器。和Charactor差不多。主要是其方法:

Stage.prototype.run = function(){
    //让礼物不断下降
    this.y += stageSpeed;
    //判断是否到达边缘
    if(this.y > LStage.height){
        this.mode = "die";
    }
    this.cheackHit();
}
Stage.prototype.cheackHit = function(){
    if(this.y > 170 && this.x > 132 - 33 && this.x < 166){
        this.mode = "die";
        point++;
        changeText();
    }else if(this.y > 170 && this.x > 293 - 33 && this.x < 330){
        this.mode = "die";
        point++;
        changeText();
    }else if(this.y > 178 && this.x > 475 - 33 && this.x < 508){
        this.mode = "die";
        point++;
        changeText();
    }
}

其实很好理解,在run中,我们让礼物向下移5格,虽然只移5格,但是如果是在onframe中调用,它将不断下降。为了判断礼物是否已经送到家,我用加入cheackHit方法。我们可以用判断坐标的方法来实现。每碰到一次就更改分数,并将mode设置为die,然后在onframe中判断mode,如果mode是”die”就移除这个对象。

 

实例化Stage类:

function addStage(){
    var stage = new Stage();
    if(oldMan.mode == "left"){
        stage.x = oldMan.x + 70;
    }else{
        stage.x = oldMan.x + 30;
    }
    stage.y = 30;
    stageLayer.addChild(stage);
    stageLayer.scaleX = 0.8;
    stageLayer.scaleY = 0.8;
}

onframe完整代码:

function onframe(event){
    showTime = Math.floor(time/1000) + "s";
    if(canSnowing == true){
        //加入雪花
        addSnow();
    }
    if(backSound.playing == false){
        //播放背景音乐
        backSound.play();
    }
    if(showChara == true){
        //使人物动起来
        oldMan.move();
        //改变时间显示
        timeText.text = "Time:" + showTime;
        if(time>0){
            time -= 30000/(30000/50);
        }else{
            playerName = getName();
            gameOver();
        }
    }
    for(var key in stageLayer.childList){
        //使用Stage中run函数,让障碍物动起来
        stageLayer.childList[key].run();
        if(stageLayer.childList[key].mode == "die"){ 
            //移除该成员
            stageLayer.removeChild(stageLayer.childList[key]);
        }
    }
}

首先我们新加了一个遍历方法,遍历LSprite成员而获取每对象的状态,每遇见一个mode是die的就将它移除。

 

接下来是加入分数以及时间的函数,没有任何逻辑。大家慢慢看就能看懂的。图片 7

function addText(){
    //加入分数文字
    pointText = new LTextField(); 
    pointText.size = 15;
    pointText.x = 10;
    pointText.y = 340;
    pointText.color = "white";
    pointText.text = "Point:" + point;
    pointText.font = "HG行書体";
    overLayer.addChild(pointText);
    //加入时间文字
    timeText = new LTextField(); 
    timeText.size = 15;
    timeText.x = 10;
    timeText.y = LStage.height - 30;
    timeText.color = "white";
    timeText.text = "Time:" + showTime;
    timeText.font = "HG行書体";
    overLayer.addChild(timeText);
    //加入滤镜
    var shadow = new LDropShadowFilter(0,45,"white",0);
    overLayer.filters = [shadow];
}
function changeText(){
    pointText.text = "Point:" + point;
}

以下是游戏结束调用的函数,同样是很简单:

function gameOver(){
    backLayer.die();
    //绘制成绩板
    gameoverLayer.graphics.drawRect(2,"dimgray",[0,0,400,300],true,"lightgray");
    gameoverLayer.x = 100;
    gameoverLayer.y = 50;
    gameoverLayer.scaleX = 0.5,
    gameoverLayer.scaleY = 0.5,
    gameoverLayer.alpha = 0.5,
    gameoverLayer.rotate = 50;
    var shadow = new LDropShadowFilter(5,45,"black",0);
    gameoverLayer.filters = [shadow];
    //通过缓动显示成绩板
    LTweenLite.to(gameoverLayer,1,{
        alpha:0.7,
        scaleX:1,
        scaleY:1,
        rotate:0,
        ease:Back.easeInOut,
        onComplete:resultFont
    });
}
function resultFont(){
    var resultArr = ["GAME OVER","Tap to Restart Game","分数:"+point,"评价:"+playerName];
    for(var i=0;i<resultArr.length;i++){
        //公有属性
        resultText = new LTextField();
        resultText.weight = "bold";
        resultText.text = resultArr[i];
        //私有有属性
        if(i==0){
            resultText.size = 30;
              resultText.color = "white";
            resultText.font = "HG行書体";
            resultText.x = 70;
            resultText.y = 20;
        }else if(i==1){
            resultText.size = 15;
              resultText.color = "white";
            resultText.font = "HG行書体";
            resultText.x = 105;
            resultText.y = 60;
        }else{
            resultText.size = 20;
              resultText.color = "white";
            resultText.font = "HG行書体";
            resultText.x = 35;
            resultText.y = 100 + (i-1)*32;    
        }
        gameoverLayer.addChild(resultText);
    }
    //加入鼠标事件
    backLayer.addEventListener(LMouseEvent.MOUSE_DOWN,function(){
        //变量清空
        point = 0;
        time = 1000*30;
        showChara = false;
        //清空全局
        backLayer.removeAllChild();
        removeChild(backLayer);
        //游戏重开
        gameInit();
        startGame()
    });
}

重开游戏的函数:

function startGame(){
    //清空画布
    logoLayer.die();
    logoLayer.removeAllChild();
    canSnowing = false;
    //加入背景
    var backBitmapdata = new LBitmapData(imglist["background"],0,0,480,360);
    var backBitmap = new LBitmap(backBitmapdata);
    backBitmap.scaleX = 1.4;
    backBitmap.scaleY = 1.4;
    sceneLayer.addChild(backBitmap);
    //加入房屋
    var houseBitmapdata = new LBitmapData(imglist["house"],0,0,480,228);
    var houseBitmap = new LBitmap(houseBitmapdata);
    houseBitmap.scaleX = 1.4;
    houseBitmap.y = 200;
    sceneLayer.addChild(houseBitmap);
    //加入人物
    addChara();
    //加入文字
    addText();
}

好了,运行一下代码:

图片 1

哈哈~~,还不错吧。

 

0~30min

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

3,源代码下载

本次开发就到这里,想了解详细代码的朋友可以看看。

下载地址:

谢谢大家阅读本文。支持就是最大的鼓励。

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();
    });
}

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

图片 9

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

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

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;
};

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

图片 10

可以看到,“数组位置”就类似于二维数组中的元素下标。储存这个位置的作用在于可以很方便地从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这个游戏引擎,实在是可以大幅提升开发效率。

源代码下载

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

发表评论

电子邮件地址不会被公开。 必填项已用*标注