开言:
以前lufy前辈写过叫“ HTML5游戏开发-零基础开发RPG游戏”的系列文章,在那里面我学习了他的引擎以及了解了游戏脚本。自从看了那几篇文章,我便对游戏开发有了基本的认识。今天我也以零基础为视点,为大家讲述如何开发一款简单的游戏。希望大家看了这篇文章,能使你对理解游戏开发有帮助。
你可以先测试一下游戏:
http://lufylegend.com/lufylegend_developers/yorhom_Christmas/index.html
1,如何进行游戏开发
1.1游戏开发思想
本文依然要运用OOP思想(Object Oriented Programming,面向对象编程),毕竟它很重要,很方便。
首先,为了让大家了解游戏开发的一些思想,我不妨说一下我对游戏开发的理解:
在游戏开发中,不是需要一个角色就要去立刻手动建设一个角色的。倘若是一个不断变化的游戏,用手慢慢改,那还不得累死。因此我们就需要用到循环或者时间轴事件。具体用循环还是时间轴事件,要看你是如何设计游戏。循环不要说,我们主要说说时间轴事件。
时间轴事件相当于一个循环,在它内部要执行的内容会不断地执行,也就是说是一个死循环。既然是死循环,那要改变游戏里的内容就会变得相当简单。只需要在外部更改界面上的属性,然后在时间轴事件内不断判断属性,执行变化即可。
1.2类的使用
另外还需要注意的是,游戏开发需要用到类。JavaScript定义类很简单,只用定义一个函数就行,属性用this来加。如下:
function people(){ this.name = "yorhom"; this.age = "13"; }
类的作用很大,加入有界面上有3个角色,我们只用循环3次,每循环一次就用局部变量来实例化一个角色类即可。
类操作里面比较重要一项的就是继承。比如说在lufylegend.js中,如果你的类继承自LSprite类,那就拥有show方法,到时候你在使用你的类时,只用更改属性,不用手动重绘就可以更新界面。除了方便以外,它还可以替你免去一些多余的代码。假如父类有个加鼠标事件的方法,而子类想要也,那就不需要在重新写一遍了,直接继承就行了。
2,开始游戏开发
2.1开发准备
由于本次开发用到了lufylegend.js开源引擎,所以首先需要下载它。
下载地址:http://lufylegend.com/lufylegend
API 文档:http://lufylegend.com/lufylegend/api
另外,由于是html5游戏,所以你需要一个支持html5的浏览器。当然,如果你已经有了这样的浏览器,那就直接开始吧。
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内部
游戏界面宽
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:"./images/airplane.png"}, {name:"logoback",path:"./images/logoback.jpg"}, {name:"background",path:"./images/background.png"}, {name:"house",path:"./images/house.png"}, {name:"costume0",path:"./images/costume0.png"}, {name:"costume1",path:"./images/costume1.png"}, {name:"costume2",path:"./images/costume2.png"}, {name:"costume3",path:"./images/costume3.png"}, {name:"costume4",path:"./images/costume4.png"}, {name:"costume5",path:"./images/costume5.png"}, {name:"costume6",path:"./images/costume6.png"}, {name:"costume7",path:"./images/costume7.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游戏引擎——第二篇,烟雨+飞雪效果
http://blog.csdn.net/yorhomwang/article/details/8915020
运行代码得到一个相当酷的界面,大家可以看一下:
光有界面也不能叫游戏,接下来就是游戏主体部分。
我们先前提到过类,现在就来用类实战一下。首先来看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的就将它移除。
接下来是加入分数以及时间的函数,没有任何逻辑。大家慢慢看就能看懂的。
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(); }
好了,运行一下代码:
哈哈~~,还不错吧。
3,源代码下载
本次开发就到这里,想了解详细代码的朋友可以看看。
下载地址:http://files.cnblogs.com/yorhom/Christmas.rar
谢谢大家阅读本文。支持就是最大的鼓励。