概述

我用lufylegend.js开发了第一个HTML5小游戏——拼图游戏,还写了篇博文来炫耀一下:HTML5小游戏《智力大拼图》发布,挑战你的思维风暴。

详细

代码下载:http://www.demodashi.com/demo/10493.html

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

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

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

一、准备工作

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

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

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

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

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代表游戏未结束,在后期的代码里,我们会看到这个变量的作用。接着我们初始化了用于表示时间和步数的time和steps这两个全局变量,另外初始化变量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;
};

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

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

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

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

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

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

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

举个例子,如果有一个数组为[3, 4, 2, 1],那么里面3 2, 3 1, 2 4, 4 1, 2 1是逆序的,所以逆序数是5。

上面我给出的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这个游戏引擎,实在是可以大幅提升开发效率。

三、文件截图以及运行效果

1、文件截图

2、双击index.html即可运行

3、运行时的截图

四、其他补充

这篇博文在最初写成的时候,我没有对逆序算法进行深入研究,再加上我的测试不仔细,我没有发现算法的错误之处。因此,在博文发布后,不少读者发现游戏无解现象并将此问题反馈给了我,经过网友热心帮助,我才找到了问题所在,并更正了算法。在此对这些热心的网友表示真心的感谢,也为我学习不深入,以及误导了不少读者而感到十分内疚自责。

注:本文著作权归作者,由demo大师(http://www.demodashi.com)宣传,拒绝转载,转载需要作者授权

速度挑战 - 2小时完成HTML5拼图小游戏相关推荐

  1. 2小时完成HTML5拼图小游戏

    当时初学游戏开发,经验浅薄,所以没有好好专研游戏里的算法和代码的缺陷,导致游戏出现了很多bug,甚至拼图打乱后很可能无法复原.最近经常有朋友问起这个游戏,希望我能把代码里的bug改一下方便初学者学习, ...

  2. 如何用HTML和css实现拼图,打造自己的html5拼图小游戏

    得益于liuyubobobo老师的canvas课程和思路指点,做了一版简单的 html5拼图小游戏,下面就简单介绍一下实现的原理... 利用canvas裁剪拼图所需的小块图片//核心代码如下: var ...

  3. 【博主推荐】html好看的拼图小游戏(附源码)

    拼图目录 html好看的拼图小游戏 1.拼图效果示意图 1.1 第一级 九宫格拼图 1.2 第二级 十六宫格拼图 1.3 第三级 三十二宫格拼图 14 第三级 八十一宫格拼图 2.图片切图说明 3.实 ...

  4. python设计拼图小游戏_教你用Python自制拼图小游戏,轻松搞定熊孩子

    摘要:本文主要为大家详细介绍了python实现拼图小游戏,文中还有示例代码介绍,感兴趣的小伙伴们可以参考一下. 开发工具 Python版本:3.6.4 相关模块: pygame模块: 以及一些Pyth ...

  5. 教你用Python自制拼图小游戏,轻松搞定熊孩子

    摘要:本文主要为大家详细介绍了python实现拼图小游戏,文中还有示例代码介绍,感兴趣的小伙伴们可以参考一下. 开发工具 Python版本:3.6.4 相关模块: pygame模块: 以及一些Pyth ...

  6. python拼图游戏编码_教你用Python自制拼图小游戏,轻松搞定熊孩子

    摘要:本文主要为大家详细介绍了python实现拼图小游戏,文中还有示例代码介绍,感兴趣的小伙伴们可以参考一下. 开发工具 Python版本:3.6.4 相关模块: pygame模块: 以及一些Pyth ...

  7. python拼图游戏代码_教你用Python自制拼图小游戏,轻松搞定熊孩子

    摘要:本文主要为大家详细介绍了python实现拼图小游戏,文中还有示例代码介绍,感兴趣的小伙伴们可以参考一下. 开发工具 Python版本:3.6.4 相关模块: pygame模块: 以及一些Pyth ...

  8. python拼图_利用python制作拼图小游戏的全过程

    开发工具 Python版本:3.6.4 相关模块: pygame模块: 以及一些Python自带的模块 关注公众号:Python学习指南,回复"拼图"即可获取源码 环境搭建 安装P ...

  9. android移动拼图小游戏的图片,利用ViewDragHelper轻松实现Android拼图游戏

    前言 最近一段时间看了一些介绍ViewDragHelper的博客,感觉这是一个处理手势滑动的神器,看完以后就想做点东西练练手,于是就做了这个Android拼图小游戏. 先上个效果图 demo.gif ...

最新文章

  1. Delphi XE2 发布了,期待了很久的东西,开始学习中。
  2. 开会迟到,代码不规范,晚上八点半去健身,跟leader坐电梯自己先出去!某新入行程序员被疯狂吐槽!...
  3. 抛弃Anchor box和NMS,目标检测新范式开源:Sparse R-CNN
  4. Linux 内核抓包功能实现基础(五) 常见问题解析
  5. 不显示参数名_第51p,万能参数与返回值,Python中函数的返回值
  6. mysql+5.6+左连接_第5章 索引与算法
  7. ubuntu中手动编译源码安装Xorg-server过程中依赖关系的解决
  8. 2012服务器在IIS部署的SLL(https)网址谷歌浏览器无法访问的问题解决
  9. 符号“”和const在函数里不同位置的用法
  10. Ubuntu下快捷方式图标存放位置
  11. 台式机机械硬盘 - 简单快捷的安装
  12. 详细解读Xbox Series X:比起堆料,它其实更在乎效率
  13. 文件排版1(C语言)
  14. 【Day 3】机器阅读理解——常见机器阅读理解模型(下)
  15. Lecture5-3Effective number of hypotheses
  16. 用Origin找两曲线的交点
  17. spring-xxx-xxx-0.0.1-SNAPSHOT.jar中没有主清单属性完美解决
  18. 机器学习第四课:SVM前置知识点(凸优化问题)
  19. HTML横向二级导航
  20. win10怎么用计算机算进制,Win10系统计算器如何转换进制-win10系统下各进制转换的方法 - 河东软件园...

热门文章

  1. python 静态方法_Python编程思想(25):方法深度解析
  2. c++ 游戏_C/C++编程笔记:C语言实现连连看游戏,项目源码分享
  3. CAN 总线嵌入式驱动编程
  4. 力控批量添加变量_力控组态软件的变量操作函数1
  5. 《深入理解 Spring Cloud 与微服务构建》第七章 负载均衡 Ribbon
  6. 微信小程序|area组件使用的地址数据文件plus
  7. Redis的SETNX
  8. Redis和Memcache区别
  9. [18/11/29] 继承(extends)和方法的重写(override,不是重载)
  10. Redis与Memcache的区别