你画像素画原创教程
HTML5(以下简称H5)干掉了Flash,H5游戏占领了浏览器。如果你想开发H5游戏,选择一款好用的开源H5引擎非常有必要。
GDevelop
GDevelop是一款2D H5游戏引擎,专为初学者和专业开发者设计。强大的事件系统,可以在不了解编程语言的情况下开发H5跨平台游戏。详细的官方教程帮助你快速上手。
下载地址:https://gdevelop-app.com/download/
Phaser
Phaser是一款2DH5游戏引擎,开发环境类似Flash。由开源开发者社区提供支持。它可以创建基于手机和桌面浏览器的游戏。有两个主要版本,即Phaser CE和Phaser 3. Phaser CE是旧版本,Phaser 3是最新的稳定版本。
下载地址:https://github.com/photonstorm/phaser
PixiJS
PixiJS是一种2D WebGL渲染器。当开发者专注于游戏开发时,引擎会自动解决设备兼容性。如果想要创建跨平台的游戏和应用,它是不错的选择。
下载地址:https://github.com/pixijs/pixi.js
Babylon.js
Babylon.js是一款3D H5游戏引擎。利用WebGL API渲染游戏。它还有一个在线沙盒,允许测试引擎API。毫无疑问,它是最好的开源HTML5和JavaScript游戏引擎之一。
下载地址:https://github.com/BabylonJS/Babylon.js
Crafty JS
Crafty JS是最好的开源H5游戏引擎之一。它的主要功能包括事件绑定,组件和实体,不需要自定义绘图或DOM操作。它还拥有一个竞争激烈的高素质开源开发者社区,他们随时可以提供帮助。
下载地址:https://github.com/craftyjs/Crafty
melonJS
melonJS是一款H5游戏轻量级引擎。这个JS库的好处是没有任何依赖性,只需要一个能够支持H5的Web浏览器。主要功能是跨平台,支持加速度和设备运动,补间效果,对象池,基本动画管理,鼠标和触摸设备支持等。
下载地址:https://github.com/melonjs/melonJS
PlayCanvas WebGL
PlayCanvas是开源3D H5游戏引擎,可以创建一些非常漂亮的3D游戏和交互式动画。使用它的公司有Facebook,三星,迪士尼,Miniclip,Mozilla,King,ARM,Zynga等。
下载地址:https://github.com/playcanvas/engine
完。
oqups
Moqups是一款免费的HTML5在线应用,可创建线框图、实体模型和UI概念。该程序使用起来非常简单,并且有内置的模板可以直接使用(模板包括单选按钮、链接、图像占位符、文本框以及滑块等)。
Stitches
Stitches是一个HTML5 sprite sheet生成器。用户只需要经过简单的拖拽图片文件到空白处,然后点击“Generate”就可以生成一个图像图标和样式表了。
HTML5 Maker
HTML5 Maker是一个在线的动画制作工具/服务,可通过HTML, HTML5, CSS和JavaScript创建动画和交互式内容。
Initializr
Initializr是一款HTML5模板生成器,帮助你快速启动基于HTML5模板的新项目。总之,Initializr 是制作 HTML5 网站最好的入门辅助开发工具,你可以使用提供的特色模板快速生成网站,也可以自定义,Initializr 会为你生成代码简洁的可定制的网页模板。
Sprite Box
Sprite Box是一款WYSIWYG工具,可帮组Web开发人员轻松、快速地创建CSS类和ID。它是基于使用背景位置属性原则来调整插入到网页块元素中的精灵小图像。这款工具是使用 JQuery,HTMl5 和 CSS3 组合来运转的,而且是一款完全免费的工具。
Liveweave
Liveweave在HTML4/HTML5、CSS2/CSS3中含有内置的自动完成上下文特性,让你工作起来更加轻松。你只需输入HTML5和CSS3 标签/元素即可,就是这么简单。
Literally Canvas
Literally Canvas是一款开源的HTML5部件,可以被集成到任何页面。它配备了一套简单的工具,包括绘图、擦除、颜色选择器、撤销、恢复、平移以及缩放。该工具利用jQuery+和Underscore.js创建而来,利用API定义背景色、工具以及尺寸。
HTML5 Demos
HTML5 Demos能够让你立即知道Firefox是否支持HTML5 Canvas,Safari能否运行HTML5 聊天客户端。
HTML5 Visual Cheat Sheet
HTML5 Visual Cheat Sheet是专为Web设计师和开发者设计的一款速查表。这份速查表包含了HTML tag列表以及支持HTML4.01/5版本的相关属性。
Switch to HTML5
Switch to HTML5是一款高效的模板生成器。如果你开始一个新的项目,记住一定要访问该网站。各种各样的HTML5网站模板将会呈现在你的眼前。
Online SVG to HTML5 Canvas Tool
这款工具可以将SVG转换成HTML5 Canvas JavaScript函数。它几乎可以运行在任何主机上,帮助用户去尝试使用Canvas,可以导出大量的矢量艺术包(Illustrator,Inkscape)。
HTML5 Test
利用HTML5 test可以测试你的浏览器支持即将推出HTML5 标准和相关规范的得分情况。尽管该规范还没有最终敲定,但是所有主流浏览器厂商都在为未来做好准备。
你可以查看浏览器支持HTML5 哪些部分并与其他浏览器进行对比。
Patternizer
Patternizer是用来创建条纹图案的生成器。
Lime JS
LimeJS 是一个 JavaScript 游戏开发框架,允许开发者创建基于 HTML5 的游戏,支持主流浏览器包括iOS。
HTML5 Reset
HTML5 Reset是(HTML,CSS等)一套设计组件,旨在帮助用户节省大量的时间,创建更加高效的项目。
HTML5 Tracker
HTML5 Tracker能够用于跟踪HTML5相关的最新修订信息。
Cross browser HTML5 forms
表单是一个网站非常重要的一部分。 HTML5的特性日历,色彩板,滑动部件,客户端验证等这些都是非常强大的工具,但现实的情况就是,大多数的浏览器不支持所有的功能。这是一个很大的问题,刚好Cross browser HTML5能够帮您如何轻松创建一个跨浏览器的表单。
学lufylegend.js之日,我用lufylegend.js开发了第一个HTML5小游戏——拼图游戏,还写了篇博文来炫耀一下:HTML5小游戏《智力大拼图》发布,挑战你的思维风暴。不过当时初学游戏开发,经验浅薄,所以没有好好专研游戏里的算法和代码的缺陷,导致游戏出现了很多bug,甚至拼图打乱后很可能无法复原。最近经常有朋友问起这个游戏,希望我能把代码里的bug改一下方便初学者学习,顺便我也打算测试一下自己写这种小游戏的速度,所以就抽出了一些时间将这个游戏从头到尾重新写了一遍,计算了一下用时,从准备、修改素材到最后完成游戏,一共用了大约2h的时间。
以下是游戏地址:
由于头条禁止在文章页面加入链接,大家私信我“拼图”即可获取下载地址。
这是我的游戏记录,欢迎各位挑战:
接下来就来讲讲如何开发完成这款游戏的。(按“编年体”)
准备lufylegend游戏引擎,大家可以去官方网站下载:
由于头条禁止在文章页面加入链接,大家私信我“拼图”即可获取下载地址。
引擎文档地址:
由于头条禁止在文章页面加入链接,大家私信我“拼图”即可获取下载地址。
可以说,如果没有强大的lufylegend引擎,这种html5小游戏用原生canvas制作,少说要一天呢。
准备素材(10min) + 修改素材(20min)。由于在下实在手残,不善于P图,修改图片用了大约20min,囧……
开发开始界面。游戏不能没有开始界面所以我们首先实现这部分代码。在此之前是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();
});
}
到此,运行代码,得到我们的开始界面:
看到这个画面,其实我自己都想吐槽一下实在是太“朴素”了,囧……
不过我这次图个制作速度,所以还望各位看官海量。
这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();
}
};
到这里,我们就实现了打乱和操作拼图块部分。
最后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这个游戏引擎,实在是可以大幅提升开发效率。
最后奉上源代码:
由于头条禁止在文章页面加入链接,大家私信我“拼图”即可获取下载地址。
这篇博文在最初写成的时候,我没有对逆序算法进行深入研究,再加上我的测试不仔细,我没有发现算法的错误之处。因此,在博文发布后,不少读者发现游戏无解现象并将此问题反馈给了我,经过网友热心帮助,我才找到了问题所在,并更正了算法。在此对这些热心的网友表示真心的感谢,也为我学习不深入,以及误导了不少读者而感到十分内疚自责。
如果大家对本文有任何意见或不解,欢迎留言~
*请认真填写需求信息,我们会在24小时内与您取得联系。