上次讲了如何快速显示一张战场地图,有了战场没有军队怎么行,本次来向战场上添加军队。一般战棋游戏中,战场上的军队有三种,我军,敌军和友军。我军是可操纵的,敌军是可攻击的,友军是不可操纵,也不可攻击的。敌军和友军之间会相互攻击。当然,一些更复杂的游戏中,以不同的势力来区分,之间的攻击关系以它们之间的敌对或同盟等关系来决定。
本脚本中的战棋游戏是以曹操传为参考对象的,军队分我军,敌军和友军,在L#脚本中,添加军队的脚本如下。
//我军添加(我军人员出战序号,面向,坐标x,坐标y,显示或隐藏) SouSouSCharacter.addOur(0,0,6,6,1); //敌军添加(人物序号,出场等级,面向,坐标x,坐标y,显示或隐藏,行动AI) SouSouSCharacter.addEnemy(1,1,0,8,4,1,0); //友军添加(人物序号,出场等级,面向,坐标x,坐标y,显示或隐藏,行动AI) SouSouSCharacter.addFriend(2,1,0,6,4,1,0);
上面的脚本分别是,我军添加,敌军添加和友军添加和相关的参数设定。
军队添加的脚本也是下面的脚本之间,且需要再addMap脚本之前。
initialization.start; initialization.end;
也就是说,军队添加的脚本正确格式如下。
initialization.start; SouSouSCharacter.addOur(0,0,6,6,1); SouSouSCharacter.addEnemy(1,1,0,8,4,1,0); SouSouSCharacter.addFriend(2,1,0,6,4,1,0); addMap(s01.smap); initialization.end;
这几个脚本的解析过程如下:
switch(lineValue.substr(0,start)){ case "addMap": LSouSouObject.sMap.addMap(lineValue.substring(start+1,end).split(",")); break; case "SouSouSCharacter.addOur": LSouSouObject.sMap.addOurCharacter(lineValue.substring(start+1,end).split(",")); break; case "SouSouSCharacter.addEnemy": LSouSouObject.sMap.addEnemyCharacter(lineValue.substring(start+1,end).split(",")); break; case "SouSouSCharacter.addFriend": LSouSouObject.sMap.addFriendCharacter(lineValue.substring(start+1,end).split(",")); break; default: LSouSouSMapScript.initialization(); }
为了实现军队的添加,需要预先准备好人物的设定,新建一个chara.json如下
{ "peo1":{ "Index":"1", "Name":"刘备", "Face":"0", "R":"0", "S":"0", "Arms":"1", "Lv":"1", "Exp":"1", "Troops":"104", "Strategy":"30", "MaxTroops":"104", "MaxStrategy":"30", "Force":"78", "Intelligence":"76", "Command":"72", "Agile":"74", "Luck":"100", "Skill":"1", "Introduction":"刘备即蜀汉昭烈帝,字玄德,汉中山靖王刘胜的后代,三国时期蜀汉开国皇帝。" }, "peo2":{ "Index":"2", "Name":"关羽", "Face":"1", "R":"1", "S":"1", "Arms":"4", "Lv":"1", "Exp":"1", "Troops":"107", "Strategy":"13", "MaxTroops":"107", "MaxStrategy":"13", "Force":"96", "Intelligence":"90", "Command":"98", "Agile":"68", "Luck":"62", "Skill":"2", "Introduction":"关羽本字长生,后改字云长。东汉末年著名将领,自刘备于乡里聚众起兵开始追随刘备,是刘备的结义兄弟,也是刘备最为信任的将领之一。" }, "peo3":{ "Index":"3", "Name":"张飞", "Face":"2", "R":"2", "S":"2", "Arms":"4", "Lv":"1", "Exp":"1", "Troops":"105", "Strategy":"9", "MaxTroops":"105", "MaxStrategy":"9", "Force":"99", "Intelligence":"46", "Command":"74", "Agile":"72", "Luck":"76", "Skill":"3", "Introduction":"张飞,字翼德,三国时期蜀汉重要将领,自刘备于乡里聚众起兵开始追随刘备,是刘备的结义兄弟,也是刘备最为信任的将领之一。" } }
chara.json文件是所有人物数据的设定,本次先暂时添加三个人物,为了方便这些人物数据的管理,先加入一个LSouSouMember类,代码如下
function LSouSouMember(data,flag){ var self = this; if(data){ self.data = data; } } LSouSouMember.prototype.getIndex = function(){ return this.data.Index; }; LSouSouMember.prototype.getS = function(){ return this.data.S; };
当然,上面并不是完整的LSouSouMember类,以后会继续慢慢扩展。曹操传的S战场剧本中,我军加入和敌友军加入有些不同,敌友军出场设定中,第一个参数需要的是人物在chara.json中的序号,而我军出场是由选定出战的我军的人员来决定的,所以第一个参数是我军人员的出场顺序号。我军出场的人员选定,也是由脚本来控制的,不过游戏开发还没到那一步,所以先预先转备好出场人员,以后再进行脚本化。在LSouSouSMap类的构造器中加入我军出场人员,并且添加一个_charaLayer层,来显示战场上的军队,以下是完整的LSouSouSMap构造器。
function LSouSouSMap(){ var self = this; base(self,LSprite,[]); LSouSouObject.sMap = self; self.nodeLength = 48; self._objectData = null; self._loadBar = null; self._mapData = null; self.mapW = 0; self.mapH = 0; self.ourlist = []; self.enemylist = []; self.friendlist = []; self._mapLayer = new LSprite(); self._charaLayer = new LSprite(); self._viewMapLayer = new LSprite(); //暂时添加,后面会脚本化 LSouSouObject.memberList.push(new LSouSouMember(LGlobal.chara["peo1"])); LSouSouObject.perWarList.push(LGlobal.chara["peo1"].Index); self.addChild(self._mapLayer); self.addChild(self._charaLayer); self.addChild(self._viewMapLayer); LGlobal.script.scriptLayer.addChild(self); LSouSouSMapScript.analysis(); }
为了实现军队的添加,给LSouSouSMap类加上下面三个函数,分别用来添加我军,敌军和友军。
LSouSouSMap.prototype.addOurCharacter = function(param){ var self = this; //判断出战人员是否合法,LSouSouObject.perWarList是我军所选出战人员 if(parseInt(param[0]) >= LSouSouObject.perWarList.length){ LSouSouSMapScript.initialization(); return; } var i,mbr,_characterS; //确定出战人员数据,LSouSouObject.memberList是加入我方的所有人员 for(i=0;i<LSouSouObject.memberList.length;i++){ mbr = LSouSouObject.memberList[i]; if(mbr.getIndex() == LSouSouObject.perWarList[parseInt(param[0])])break; } //将出战人员加入到战场中的_charaLayer层 _characterS = new LSouSouCharacterS(mbr,0,parseInt(param[1]),0); self._charaLayer.addChild(_characterS); //出战人员位置确定 _characterS.x = parseInt(param[2])*LSouSouObject.sMap.nodeLength; _characterS.y = parseInt(param[3])*LSouSouObject.sMap.nodeLength; //出战人员移动目标,人物移动是会用到 _characterS.tagerCoordinate = new LPoint(_characterS.x,_characterS.y); //出战人员是否隐藏设定 if(parseInt(param[4]) == 0)_characterS.visible = false; //将出战人员添加到我军人员ourlist数组 LSouSouObject.sMap.ourlist.push(_characterS); LSouSouSMapScript.initialization(); }; LSouSouSMap.prototype.addEnemyCharacter = function(param){ var self = this; var i,mbr,_characterS; //确定出战人员数据 var memberData = LGlobal.chara["peo"+param[0]]; mbr = new LSouSouMember(memberData); //将出战人员加入到战场中的_charaLayer层 _characterS = new LSouSouCharacterS(mbr,1,parseInt(param[2]),parseInt(param[6])); self._charaLayer.addChild(_characterS); //出战人员位置确定 _characterS.x = parseInt(param[3])*LSouSouObject.sMap.nodeLength; _characterS.y = parseInt(param[4])*LSouSouObject.sMap.nodeLength; //出战人员移动目标,人物移动是会用到 _characterS.tagerCoordinate = new LPoint(_characterS.x,_characterS.y); //出战人员是否隐藏设定 if(parseInt(param[5]) == 0)_characterS.visible = false; //将出战人员添加到敌军人员enemylist数组 LSouSouObject.sMap.enemylist.push(_characterS); LSouSouSMapScript.initialization(); }; LSouSouSMap.prototype.addFriendCharacter = function(param){ var self = this; var i,mbr,_characterS; //确定出战人员数据 var memberData = LGlobal.chara["peo"+param[0]]; mbr = new LSouSouMember(memberData); //将出战人员加入到战场中的_charaLayer层 _characterS = new LSouSouCharacterS(mbr,1,parseInt(param[2]),parseInt(param[6])); self._charaLayer.addChild(_characterS); //出战人员位置确定 _characterS.x = parseInt(param[3])*LSouSouObject.sMap.nodeLength; _characterS.y = parseInt(param[4])*LSouSouObject.sMap.nodeLength; //出战人员移动目标,人物移动是会用到 _characterS.tagerCoordinate = new LPoint(_characterS.x,_characterS.y); //出战人员是否隐藏设定 if(parseInt(param[5]) == 0)_characterS.visible = false; //将出战人员添加到友军人员friendlist数组 LSouSouObject.sMap.friendlist.push(_characterS); LSouSouSMapScript.initialization(); };
LSouSouCharacterS类,是战场上的人物类,包含战场上人物的所有行动控制,以及AI属性等,在lufylegend.js引擎中,显示人物动画,一般是用LAnimation类来实现的,但是用LAnimation类来显示动画的时候,使用的图片必须是一张图片,也就是说,人物的所需要播放的动作需要绘制到一张图片上,然后进行切割播放。但是,曹操传中,战场上的人物图片一共分为下面三个
因为要显示的动画,分别分配在三张图片中,所以用LAnimation类来实现的话,就有点麻烦了,所以,这里显示动画,就直接用LBitmap来实现了,只要控制好LBitmap中的LBitmapData对象,同样可以完成动画的显示。看下面的代码
function LSouSouCharacterS(member,belong,direct,command){ var self = this; base(self,LSouSouCharacter,[]); self._member = member; self._belong = belong; self._direction = direct; self._command = command; self.animeList = []; self.action = 0; self.actionIndex = 0; self.image = {}; self.image["atk"] = new LBitmapData(LGlobal.imglist["s-view-atk"]); self.image["mov"] = new LBitmapData(LGlobal.imglist["s-view-mov"]); self.image["spc"] = new LBitmapData(LGlobal.imglist["s-view-spc"]); self.image["mov"].setProperties(0,0,48,48); self.bitmap = new LBitmap(self.image["mov"]); self.addChild(self.bitmap); self.setImage(); } LSouSouCharacterS.prototype.setImage = function(){ var self = this; var atklist = LGlobal.divideCoordinate(self.image["atk"].image.width,self.image["atk"].image.height,12,1); var movlist = LGlobal.divideCoordinate(self.image["mov"].image.width,self.image["mov"].image.height,11,1); var spclist = LGlobal.divideCoordinate(self.image["spc"].image.width,self.image["spc"].image.height,5,1); var list; /*********站立*********/ //下 list = [ "mov", 48, 48, [[movlist[6][0],1]] ]; self.animeList.push(list); //左 list = [ "mov", 48, 48, [[movlist[8][0],1]] ]; self.animeList.push(list); //上 list = [ "mov", 48, 48, [[movlist[7][0],1]] ]; self.animeList.push(list); //右 list = [ "mov", 48, 48, [[movlist[8][0],-1]] ]; self.animeList.push(list); /*********移動*********/ //下 list = [ "mov", 48, 48, [ [movlist[0][0], 1], [movlist[1][0], 1] ] ]; self.animeList.push(list); //左 list = [ "mov", 48, 48, [ [movlist[4][0], 1], [movlist[5][0], 1] ] ]; self.animeList.push(list); //上 list = [ "mov", 48, 48, [ [movlist[2][0], 1], [movlist[3][0], 1] ] ]; self.animeList.push(list); //右 list = [ "mov", 48, 48, [ [movlist[4][0], -1], [movlist[5][0], -1] ] ]; self.animeList.push(list); self.action = self._direction+4; };
在一个战棋游戏中,战场上的军队可以说是多种多样,每个兵种所用的图片都是不同的,而特殊的人物也有其特殊的形象,所用的图片也是不同的。这样一来,所需要的图片就是比较多的,一个游戏中所用的战场形象可能有几十个甚至上百个,要想快速进入战场的话,就需要预先读取所有形象。在本地游戏中,读取这些图片也是不需要多少时间的,可是在页游上就需要注意了。所以我在添加战场人物LSouSouCharacterS时,预先将人物形象设定为准备好的形象图片,运行后的效果如下。
LSouSouCharacterS类的setImage函数,是将三张人物形象的动作进行分解,将【站立】,【移动】,【攻击】,【防御】......等每一个动作都压入到animeList数组中。本次暂时分解【站立】,【移动】两个动作。前面说了,本次显示人物动画,是直接使用LBitmap对象,通过控制LBitmap对象中的LBitmapData对象来实现动画的播放。在setImage函数中被压入animeList数组中的元素依次为[LBitmapData对象名称,形象的宽,形象的高,动画坐标组 ],其中的动画坐标组是由图片区域坐标以及图片是否翻转组成的一组序列,具体为[ [区域坐标,图片是否翻转], [区域坐标,图片是否翻转] ],这里的区域坐标不太好解释,举个例子,比如下面这个图片。
图中红色矩形框为显示区域,由于每个小动作图片的大小为48x48,则区域坐标为(0,48)。这样一来,如果要播放动画,则将animeList中的相应的动作,对应的区域坐标数组中的元素,依次替换当前的人物的LBitmapData对象,就会实现动画,具体实现的话,先要添加LEvent.ENTER_FRAME事件如下。
self.speed = 2; self.speedIndex = 0; self.addEventListener(LEvent.ENTER_FRAME,self.onframe);
onframe函数代码如下
LSouSouCharacterS.prototype.onframe = function(self){ if(self.speedIndex++ < self.speed)return; self.speedIndex = 0; var anime = self.animeList[self.action] var list = anime[3]; if(self.actionIndex >= list.length){ self.actionIndex = 0; } var obj = list[self.actionIndex++]; var coordinate = obj[0]; self.bitmap.bitmapData = self.image[anime[0]]; self.bitmap.bitmapData.setProperties(coordinate.x,coordinate.y,anime[1],anime[2]); self.bitmap.scaleX = obj[1]; };
以上代码,虽然实现了动画的播放,但是所用的图片依然是预先设定的黑色动作图片,所有人物都是一样的,如果要实现战场人物的多样化,只需要根据chara.json数据中的设定,读取相应的战场形象图片,待读取完图片之后,替换掉LSouSouCharacterS类中原来所用的图片就可以了,具体实现如下。
LSouSouCharacterS.prototype.loadImage = function(){ var self = this; atkLoader = new LLoader(); atkLoader.parent = self; atkLoader.addEventListener(LEvent.COMPLETE,self.loadAtkOver); atkLoader.load("images/characters/s/" + self._member.getS() +"/atk.png" + (LGlobal.traceDebug?("?"+(new Date()).getTime()):""),"bitmapData"); movLoader = new LLoader(); movLoader.parent = self; movLoader.addEventListener(LEvent.COMPLETE,self.loadMovOver); movLoader.load("images/characters/s/" + self._member.getS() +"/mov.png" + (LGlobal.traceDebug?("?"+(new Date()).getTime()):""),"bitmapData"); spcLoader = new LLoader(); spcLoader.parent = self; spcLoader.addEventListener(LEvent.COMPLETE,self.loadSpcOver); spcLoader.load("images/characters/s/" + self._member.getS() +"/spc.png" + (LGlobal.traceDebug?("?"+(new Date()).getTime()):""),"bitmapData"); }; LSouSouCharacterS.prototype.loadAtkOver = function(event){ var self = event.target.parent; self.image["atk"] = new LBitmapData(event.currentTarget); }; LSouSouCharacterS.prototype.loadMovOver = function(event){ var self = event.target.parent; self.image["mov"] = new LBitmapData(event.currentTarget); }; LSouSouCharacterS.prototype.loadSpcOver = function(event){ var self = event.target.parent; self.image["spc"] = new LBitmapData(event.currentTarget); };
人物形象读取完之后,将图片替换后的效果如下
测试连接如下
http://lufylegend.com/demo/test/lsharp/09/game/index.html
以上,本章就先讲这么多了,下一章讲一讲战场上的寻路算法,让军队在战场上进行移动。
本章为止的源码如下,不包含lufylegend.js引擎源码,请自己到官网下载
http://lufylegend.com/demo/test/lsharp/09/09.rar
※源码运行说明:需要服务器支持,详细请看本系列文章《序》和《第一章》
《游戏脚本的设计与开发》系列文章目录
http://blog.csdn.net/lufy_legend/article/details/8888787