上一篇《游戏脚本的设计与开发》-序中我介绍了游戏脚本的基本概念和准备工作,本篇来说说具体如何解析一个脚本
所谓解析脚本,就是按照自己定义的语法,将每一个脚本命令还原成不同的代码逻辑进行执行,比如,我规定绘制一个矩形的脚本
draw rect和一个绘制圆的脚本
draw arc那么,当我读取到了字符串“draw rect”的时候,就在屏幕上画一个矩形,读取到了字符串“draw arc”的时候,就在屏幕上画一个圆。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>L#</title> </head> <body> <div id="mylegend">loading......</div> <script type="text/javascript" src="./lufylegend-1.7.5.js"></script> <script type="text/javascript" src="./lufylegend.lsharp.js"></script> <script type="text/javascript" src="./Main.js"></script> </body> </html>
/** LScript.js **/ function LScript(scriptLayer,value){ var self = this; LGlobal.script = self; self.scriptLayer = scriptLayer; self.dataList = new Array(); var arr=[value]; self.dataList.unshift(arr); self.toList(value); }
LGlobal.script = self;LGlobal.script将脚本解析对象保存起来,方便回调脚本的解析函数。
self.scriptLayer = scriptLayer;将显示层保存起来,方便以后使用。
self.dataList = new Array(); var arr=[value]; self.dataList.unshift(arr);保存脚本到缓存数组。
self.toList(value);这一行是将整个字符串脚本进行分解,得到单个的脚本命令数组。
toList:function(ltxt){ var self = this; self.lineList = ltxt.split(";"); self.copyList = self.lineList.slice(0); self.analysis(); }代码解析:
self.lineList = ltxt.split(";");在L#中,我设定了各个的脚本指令之间是以分号来分割的,所以使用字符串的split函数对字符串进行分割,得到单个的脚本命令数组,并将其存入到脚本解析对象的lineList。
self.copyList = self.lineList.slice(0);copyList用来保存当前所执行的lineList,当有新的脚本命令lineList被添加进来的时候,当前的lineList就会被保存起来,当新的脚本命令lineList全部解析完之后,就会对上一个未被解析完的脚本命令继续进行解析执行。
self.analysis();开始解析脚本命令。
analysis:function(){ var self = this; var lineValue = ""; if(self.lineList.length == 0){ self.dataList.shift(); if(self.dataList.length > 0){ arr=self.dataList[0]; self.lineList = arr[1]; self.copyList = arr[2]; self.analysis(); } return; } while(self.lineList.length > 0){ lineValue = LMath.trim(self.lineList[0]); self.lineList.shift(); } if(lineValue.length == 0){ self.analysis(); return; } trace("analysis lineValue = " + lineValue); var sarr = lineValue.split("."); switch(sarr[0]){ default: self.analysis(); } }代码解析:
if(self.lineList.length == 0){ self.dataList.shift(); if(self.dataList.length > 0){ arr=self.dataList[0]; self.lineList = arr[1]; self.copyList = arr[2]; self.analysis(); } return; }上面代码判断lineList数组内的脚本指令是不是被解析完,如果被解析完了的话,检查一下dataList数组中是否有其他未被执行的脚本,有则继续执行。
var lineValue = ""; while(self.lineList.length > 0 && lineValue.length == 0){ lineValue = LMath.trim(self.lineList[0]); self.lineList.shift(); }上面代码用来获取一个lineList数组中的脚本指令
if(lineValue.length == 0){ self.analysis(); return; }如果当前脚本指令为空,则执行下一条指令
trace("analysis lineValue = " + lineValue);这行代码用来debug,发布的时候可以不要
var sarr = lineValue.split("."); switch(sarr[0]){ default: self.analysis(); }在L#中,所有被执行的脚本指令,都是[类.命令]的格式,如果你在设定脚本的时候使用了其他格式,则请根据你的格式来做相应的处理。所以使用split函数,对每一条指令进行分割,获取需要的信息,进行解析。
init(50,"mylegend",400,100,main); function main(){ LGlobal.setDebug(true); var sc = "aaa;Load.script(script/Main.ls);bbb;"; var sp = new LSprite(); addChild(sp); var script = new LScript(sp,sc); }那么脚本解析类会对["aaa;Load.script(script/Main.ls);bbb;"]这个脚本进行解析,里面的脚本指令是我随便写的,并非正确的指令。
你本地的执行URL为http://localhost/lsharp/index.html
图1
可以看到,解析类对脚本进行了解析,按照分号将脚本分割为了三个指令,那么接下来就需要增加对每一条指令的解析,当出现了无法解析的指令的时候,会自动跳过对下一个指令进行解析。Load.script(script/Main.ls);并且在script文件夹中新建一个Main.ls文件,用记事本打开Main.ls文件写入以下内容
Text.label(-,txt,测试a,0,0,30,#000000); Text.label(-,txt,测试b,0,0,30,#000000);
switch(sarr[0]){ case "Load": ScriptLoad.analysis(lineValue); break; default: self.analysis(); }所以,现在需要一个静态类ScriptLoad,来对Load指令进行解析,新建一个ScriptLoad类,如下
/* * ScriptLoad.js **/ var ScriptLoad = function (){}; ScriptLoad.data = ""; ScriptLoad.urlloader = null; ScriptLoad.analysis = function (value){ var start = value.indexOf("("); var end = value.indexOf(")"); ScriptLoad.data = value.substring(start+1,end).split(","); switch(LMath.trim(value.substr(0,start))){ case "Load.script": ScriptLoad.loadScript(); break; default: LGlobal.script.analysis(); } };ScriptLoad类的解析函数analysis中,首先将脚本中括号外和括号内的内容[Load.script]和[script/Main.ls]分解出来,然后再利用switch函数进一步解析。当遇到字符串“Load.script”的时候,调用loadScript函数来读取一个脚本文件。
ScriptLoad.loadScript = function (){ ScriptLoad.urlloader = new LURLLoader(); ScriptLoad.urlloader.addEventListener(LEvent.COMPLETE,ScriptLoad.loadScriptOver); ScriptLoad.urlloader.load(ScriptLoad.data[0],"text"); }; ScriptLoad.loadScriptOver = function (event){ var script = LGlobal.script; var data = event.target.data; ScriptLoad.urlloader.die(); ScriptLoad.urlloader = null; script.saveList(); script.dataList.unshift([data]); script.toList(data); };代码解析
script.saveList(); script.dataList.unshift([data]); script.toList(data);其中的saveList函数如下
saveList:function(){ var self = this; var arr=self.dataList[0]; if(arr){ arr[1]=self.lineList; arr[2]=self.copyList; } }所做的处理就是前面所说的,将当前正在执行的脚本数组保存起来
运行代码,debug输出以下信息
图2
可以看到,Main.ls脚本文件中的脚本指令都被解析了出来,只是Text.label这个脚本我们还没有对其进行解析,所以只是将其跳过了。Text.label(-,txt,测试a,0,0,30,#000000); Load.script(script/test.ls); Text.label(-,txt,测试b,0,0,30,#000000);并且,再添加一个test.ls脚本文件,test.ls中的内容如下
Text.label(-,txt,测试c,0,0,30,#000000);运行一下程序,看debug输出如下信息:
图3
可以看到,test.ls中的脚本指令也被读取出来了。测试连接
http://lufylegend.com/demo/test/lsharp/01/index.html
init(50,"mylegend",400,100,main); function main(){ LGlobal.setDebug(true); var sc = "Load.script(script/Main.ls);"; var sp = new LSprite(); addChild(sp); var script = new LScript(sp,sc); }lufylegend.lsharp.js
/* * LScript.js **/ function LScript(scriptLayer,value){ var self = this; LGlobal.script = self; self.scriptLayer = scriptLayer; self.dataList = new Array(); var arr=[value]; self.dataList.unshift(arr); self.toList(value); } LScript.prototype = { toList:function(ltxt){ var self = this; self.lineList = ltxt.split(";"); self.copyList = self.lineList.slice(0); self.analysis(); }, saveList:function(){ var self = this; var arr=self.dataList[0]; if(arr){ arr[1]=self.lineList; arr[2]=self.copyList; } }, analysis:function(){ var self = this; var arr; if(self.lineList.length == 0){ self.dataList.shift(); if(self.dataList.length > 0){ arr=self.dataList[0]; self.lineList = arr[1]; self.copyList = arr[2]; self.analysis(); } return; } var lineValue = ""; while(self.lineList.length > 0 && lineValue.length == 0){ lineValue = LMath.trim(self.lineList[0]); self.lineList.shift(); } if(lineValue.length == 0){ self.analysis(); return; } trace("analysis lineValue = " + lineValue); var sarr = lineValue.split("."); switch(sarr[0]){ case "Load": ScriptLoad.analysis(lineValue); break; default: self.analysis(); } } }; /* * ScriptLoad.js **/ var ScriptLoad = function (){}; ScriptLoad.data = ""; ScriptLoad.urlloader = null; ScriptLoad.analysis = function (value){ var start = value.indexOf("("); var end = value.indexOf(")"); ScriptLoad.data = value.substring(start+1,end).split(","); switch(LMath.trim(value.substr(0,start))){ case "Load.script": ScriptLoad.loadScript(); break; default: LGlobal.script.analysis(); } }; ScriptLoad.loadScript = function (){ ScriptLoad.urlloader = new LURLLoader(); ScriptLoad.urlloader.addEventListener(LEvent.COMPLETE,ScriptLoad.loadScriptOver); ScriptLoad.urlloader.load(ScriptLoad.data[0],"text"); }; ScriptLoad.loadScriptOver = function (event){ var script = LGlobal.script; var data = event.target.data; ScriptLoad.urlloader.die(); ScriptLoad.urlloader = null; script.saveList(); script.dataList.unshift([data]); script.toList(data); };