源码下载地址:http://code.google.com/p/loon-simple/downloads/list

根据wikipedia的解释:脚本语言(Script language,scripting language,scripting programming language),是为了缩短传统的编写-编译-链接-运行(edit-compile-link-run)过程而创建的计算机编程语言。此命名起源于一个脚本“screenplay”,每次运行都会使对话框逐字重复。早期的脚本语言经常被称为批次处理语言或工作控制语言。一个脚本通常是解释运行而非编译。脚本语言通常都有简单、易学、易用的特性,目的就是希望能让程序设计师快速完成程序的编写工作。

对于脚本语言,想必我们这些Java程序员都再熟悉不过,因为我们每天都在使用的Java,从严格意义上讲也是一种解释型语言——毕竟class是凭借JVM进行字节码解释才得以执行,故而长期被排斥于编译型语言的门槛之外(但是,由于Java有编译过程,若CPU支持字节码那么class就可以脱离JVM限制,说Java是纯粹的解释型语言似乎也很不妥+︿+……期待java cpu普及中……)。

在这个讲解AVG游戏开发的系列博文中,为什么会提到脚本引擎的制作及应用呢?笔者将在下面加以说明。

在游戏开发中,我们将无可避免的遇到很多重复性工作,比如角色A-Z的创建,物品0-n的编辑,对话x->+∞的添加等等——这些并不是需要复杂的编码,甚至仅是单纯的数据录入,但由于数据量庞大,通常也会耗费相当的时间进行设定。

如果让您来做,要怎么办呢?以最直观的方式来说,可能有些人会考虑硬编码,直接编码似乎能省去很多的麻烦事。

OK,或许我们完全可以采过硬编码方式解决问题,比如建立一个对应物品的class,而后为其添加各种对应字段。

那么,假设现在我们在某个同人游戏的开发中这样干了,并且将游戏对外发布。三天后,问题来了,有人反映某物品——我虚构个名字,就叫[对抗网瘾专用电棍]吧。有人反映它杀伤力太低,且对[网瘾]状态无伤害加成……嗯,我们考虑后认为有道理,怎么办呢?一个字——改,反正就是一两行代码的事情,重新编译,打包,发布——简单简单。

这下高枕无忧了吧?可惜,出乎我们意料之外的事情又再次发生,又过了两天,有玩家反映我们的最终BOSS[真.红色有角.砖家叫兽地狱鬼畜模式]的大招[终极网瘾扩散]会将所有我方角色全体[网瘾化],并且会持续投掷[对抗网瘾专用电棍]攻击我方角色,由于改版后的[对抗网瘾专用电棍]攻击力太强,并对[网瘾]状态有极强的伤害加成,我方角色很容易便会被BOSS团灭。

你问我这下怎么办?硬编码的还有什么新鲜,只能再重新编译……用户就是上帝,上帝让改就改吧……但是,尽管我们非常努力,却总有欠缺在这个游戏中存在,如果这样改下去,三次、四次、十次、二十次|||,次次都需要重新编译及发布,就是再简单也受不了了(做Java还好,搞C/C++的很多人都经历过守夜等编译结果吧……),恐怕最终此游戏只好停止发布,烂尾了事。如果你将每个物品都直接编码到游戏当中,那么毫无疑问你将会陷入到噩梦一般的处境。

为什么我们不能选择更加简单的方式来处理呢?既然每次重新编译、重新发布程序那么麻烦,干什么不将配置放在游戏外面呢?实际上,我们完全可以创建一个文本文件记录下所有物品参数,而每次更新时,我们仅需修改此文本文件便可以改变整个游戏中的属性设置,再也不必事无巨细都重新编译、重新发布整个游戏了。

事实上,笔者之所以耗费笔墨写这么一个例子,无非是为了说明脚本应用背后的基本准则——简化软件制作流程,避免硬编码。

脚本可以使你真正在游戏引擎之外编写代码,然后再将这些代码加载到游戏引擎之中并对它加以执行。一般情况下,脚本是按照它们自己的语言格式进行编写的,使用何种语法完全由脚本引擎决定,脚本就好像是运行在游戏程序内部的小程序,它们的工作原理和其他的普通程序一样,你可以使用一个普通的文本编辑器来编写它们,然后在你的主程序中执行它们。脚本通常使用它们自己的编译器或者解释器,它们对游戏主引擎并没有任何影响——除非你希望它这么做。

同时,游戏和脚本之间通常是分离的。你可以像加载图形、声音,甚至早期的物体描述文件一样加载脚本。但是,你并不能在显示器上将它们显示出来或者是通过喇叭把它们播放出来,取而代之的是,你可以执行它们,它们也可以和你的游戏进行通信,而游戏也可以作出应答。

简单的说,如果我们将游戏引擎和游戏数据理解为人与积木,那么脚本就是用来搭建积木的图样;如果我们将游戏引擎和游戏数据理解为海洋与大陆,那么脚本就是通行在海洋与大陆间传递资源的货轮。

游戏脚本是游戏引擎不可或缺的最佳搭档。

可能您会问,既然脚本有这么多的好处,想必制作起来很麻烦吧?

关于这点,需要从不同的角度来加以考虑。如果我们以groovy、ruby、python、lua、angelscript这些已经成熟或接近成熟的脚本语言为标准来看,那么开发一种脚本语言的确是件很复杂的事情,涉及到易用性、执行效率、安全性、资源回收、并发等等许多的“额外”问题,而且既然有groovy、ruby、python、lua、angelscript这林林种种,我们也大可不必耗费心智来重复发明轮子。

但是,如果我们从与游戏引擎交互,减少游戏代码量的角度出发,那么自己设计的脚本与自己游戏引擎间的藕合性当然会较第三方的脚本为高,代码量也当然会更少,而且维护管理也更为简便。况且交给脚本来执行的部分,通常也不会对运行效率有太高的要求,我们与大师的差异,也仅仅是体现在决定成败的“细节”方面,单就构建脚本解释器本身而言,完全算不得难事,甚至简单到一两个小时就可以搞定一个具有初步功能的脚本引擎(别说脚本,很多80后乃至90后的程序员都开始自己写编译器玩了|||……)。

比如TJS(就是做吉里吉里那个)及RGSS (Ruby Game Scripting System)等,都是为了更加适应自身游戏引擎而开发出来的游戏脚本语言。

下面我将提供一个非常简单的脚本解释器代码,主要功能被集中在一个Command类中。

package org.loon.framework.game.command; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import org.loon.framework.game.LSystem; import org.loon.framework.game.collection.ArrayMap; import org.loon.framework.game.resource.ResourceLoader; /** * Copyright 2008 - 2009 * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * * @project loonframework * @author chenpeng * @email??ceponline@yahoo.com.cn * @version 0.1 */ public class Command extends Conversion implements Serializable { /** * */ private static final long serialVersionUID = 1L; final static private Map cacheScript = Collections .synchronizedMap(new HashMap(1500)); private String cacheCommandName; // 函数列表 final public ArrayMap functions = new ArrayMap(20); // 变量列表 final Map setEnvironmentList = Collections.synchronizedMap(new HashMap(20)); // 条件分支列表 ArrayMap conditionEnvironmentList = new ArrayMap(30); // 读入连续数据 final static private StringBuffer reader = new StringBuffer(3000); // 注释标记中 private boolean flaging = false; // 判断标记中 private boolean ifing = false; // 函数标记中 private boolean functioning = false; // 分支标记 private boolean esleflag = false; private boolean backIfBool = false; private String executeCommand; private String nowPosFlagName; private boolean addCommand; private boolean isInnerCommand; private boolean isRead; private boolean isCall; private boolean isCache; private boolean if_bool; private boolean elseif_bool; private Command innerCommand; private String commandString; private List temps; private List printTags; private List randTags; private int scriptSize; private int offsetPos; // 脚本数据列表 private List scriptList; // 脚本名 private String scriptName; /** * 构造函数,载入指定脚本文件 * * @param fileName */ public Command(String fileName) { formatCommand(fileName); } /** * 构造函数,载入指定list脚本 * * @param resource */ public Command(String fileName, List resource) { formatCommand("function", resource); } public void formatCommand(String fileName) { formatCommand(fileName, Command.includeFile(fileName)); } public void formatCommand(String name, List resource) { setEnvironmentList.clear(); conditionEnvironmentList.clear(); // 默认选择项 setEnvironmentList.put(V_SELECT_KEY, "-1"); scriptName = name; scriptList = resource; scriptSize = scriptList.size(); offsetPos = 0; flaging = false; ifing = false; isCache = true; esleflag = false; backIfBool = false; } private boolean setupIF(String commandString, String nowPosFlagName, Map setEnvironmentList, Map conditionEnvironmentList) { boolean result = false; conditionEnvironmentList.put(nowPosFlagName, new Boolean(false)); try { List temps = commandSplit(commandString); Object valueA = (String) temps.get(1); Object valueB = (String) temps.get(3); valueA = setEnvironmentList.get(valueA) == null ? valueA : setEnvironmentList.get(valueA); valueB = setEnvironmentList.get(valueB) == null ? valueB : setEnvironmentList.get(valueB); // 非纯数字 if (!isNumber(valueB)) { try { // 尝试四则运算公式匹配 valueB = compute.parse(valueB); } catch (Exception e) { } } String condition = (String) temps.get(2); // 无法判定 if (valueA == null || valueB == null) { conditionEnvironmentList .put(nowPosFlagName, new Boolean(false)); } // 相等 if ("==".equals(condition)) { conditionEnvironmentList.put(nowPosFlagName, new Boolean( result = valueA.toString().equals(valueB.toString()))); // 非等 } else if ("!=".equals(condition)) { conditionEnvironmentList.put(nowPosFlagName, new Boolean( result = !valueA.toString().equals(valueB.toString()))); // 大于 } else if (">".equals(condition)) { int numberA = Integer.parseInt(valueA.toString()); int numberB = Integer.parseInt(valueB.toString()); conditionEnvironmentList.put(nowPosFlagName, new Boolean( result = numberA > numberB)); // 小于 } else if ("<".equals(condition)) { int numberA = Integer.parseInt(valueA.toString()); int numberB = Integer.parseInt(valueB.toString()); conditionEnvironmentList.put(nowPosFlagName, new Boolean( result = numberA < numberB)); // 大于等于 } else if (">=".equals(condition)) { int numberA = Integer.parseInt(valueA.toString()); int numberB = Integer.parseInt(valueB.toString()); conditionEnvironmentList.put(nowPosFlagName, new Boolean( result = numberA >= numberB)); // 小于等于 } else if ("<=".equals(condition)) { int numberA = Integer.parseInt(valueA.toString()); int numberB = Integer.parseInt(valueB.toString()); conditionEnvironmentList.put(nowPosFlagName, new Boolean( result = numberA <= numberB)); } } catch (Exception e) { e.printStackTrace(); } return result; } /** * 打开脚本缓存 * */ public void openCache() { isCache = true; } /** * 关闭脚本缓存 * */ public void closeCache() { isCache = false; } /** * 当前脚本行缓存名 * * @return */ public String nowCacheOffsetName() { return (scriptName + FLAG + offsetPos + FLAG + commandString) .toLowerCase(); } /** * 重启脚本缓存 * */ public static void resetCache() { cacheScript.clear(); } public boolean isRead() { return isRead; } public void setRead(boolean isRead) { this.isRead = isRead; } /** * 返回当前的读入数据集合 * * @return */ public synchronized String[] getReads() { String result = reader.toString(); result = result.replace(SELECTS_TAG, ""); return split(result, FLAG); } /** * 返回指定索引的读入数据 * * @param index * @return */ public synchronized String getRead(int index) { try { return getReads()[index]; } catch (Exception e) { return null; } } /** * 截取第一次出现的指定标记 * * @param messages * @param startString * @param endString * @return */ public static String getNameTag(String messages, String startString, String endString) { List results = getNameTags(messages, startString, endString); return (results == null || results.size() == 0) ? null : (String) results.get(0); } /** * 截取指定标记内容为list * * @param messages * @param startString * @param endString * @return */ public static List getNameTags(String messages, String startString, String endString) { return Command.getNameTags(messages.toCharArray(), startString .toCharArray(), endString.toCharArray()); } /** * 截取指定标记内容为list * * @param messages * @param startString * @param endString * @return */ public static List getNameTags(char[] messages, char[] startString, char[] endString) { int dlength = messages.length; int slength = startString.length; int elength = endString.length; List tagList = new ArrayList(10); boolean lookup = false; int lookupStartIndex = 0; int lookupEndIndex = 0; int length; StringBuffer sbr = new StringBuffer(100); for (int i = 0; i < dlength; i++) { char tag = messages[i]; if (tag == startString[lookupStartIndex]) { lookupStartIndex++; } if (lookupStartIndex == slength) { lookupStartIndex = 0; lookup = true; } if (lookup) { sbr.append(tag); } if (tag == endString[lookupEndIndex]) { lookupEndIndex++; } if (lookupEndIndex == elength) { lookupEndIndex = 0; lookup = false; length = sbr.length(); if (length > 0) { tagList.add(sbr.substring(1, sbr.length() - elength)); sbr.delete(0, length); } } } return tagList; } /** * 注入选择变量 * * @param type */ public void select(int type) { if (innerCommand != null) { innerCommand.setVariable(V_SELECT_KEY, String.valueOf(type)); } setVariable(V_SELECT_KEY, String.valueOf(type)); } public String getSelect() { return (String) getVariable(V_SELECT_KEY); } /** * 插入变量 * * @param key * @param value */ public void setVariable(String key, Object value) { setEnvironmentList.put(key, value); } /** * 插入变量集合 * * @param vars */ public void setVariables(Map vars) { setEnvironmentList.putAll(vars); } /** * 返回变量集合 * * @return */ public Map getVariables() { return setEnvironmentList; } public Object getVariable(String key) { return setEnvironmentList.get(key); } /** * 删除变量 * * @param key */ public void removeVariable(String key) { setEnvironmentList.remove(key); } /** * 判定脚本是否允许继续解析 * * @return */ public boolean next() { return (offsetPos < scriptSize); } /** * 跳转向指定索引位置 * * @param offset * @return */ public boolean gotoIndex(final int offset) { boolean result = offset < scriptSize && offset > 0 && offset != offsetPos; if (result) { offsetPos = offset; } return result; } /** * 批处理执行脚本,并返回可用list结果 * * @return */ public List batchToList() { List reslist = new ArrayList(scriptSize); for (; next();) { String execute = doExecute(); if (execute != null) { reslist.add(execute); } } return reslist; } /** * 批处理执行脚本,并返回可用string结果 * * @return */ public String batchToString() { StringBuffer resString = new StringBuffer(scriptSize * 10); for (; next();) { String execute = doExecute(); if (execute != null) { resString.append(execute); resString.append("\n"); } } return resString.toString(); } private void setupSET() { if (commandString.startsWith(SET_TAG)) { List temps = commandSplit(commandString); int len = temps.size(); String result = null; if (len == 4) { result = temps.get(3).toString(); } else if (len > 4) { StringBuffer sbr = new StringBuffer(len); for (int i = 3; i < temps.size(); i++) { sbr.append(temps.get(i)); } result = sbr.toString(); } if (result != null) { // 替换已有变量字符 Set set = setEnvironmentList.entrySet(); for (Iterator it = set.iterator(); it.hasNext();) { Entry entry = (Entry) it.next(); result = replaceMatch(result, (String) entry.getKey(), entry.getValue().toString()); } // 当为普通字符串时 if (result.startsWith("\"") && result.endsWith("\"")) { setEnvironmentList.put(temps.get(1), result.substring(1, result.length() - 1)); } else if (isChinese(result) || isEnglishAndNumeric(result)) { setEnvironmentList.put(temps.get(1), result); } else { // 当为数学表达式时 setEnvironmentList.put(temps.get(1), compute.parse(result)); } } addCommand = false; } } /** * 随机数处理 * */ private void setupRandom() { // 随机数判定 if (commandString.indexOf(RAND_TAG) != -1) { randTags = Command.getNameTags(commandString, RAND_TAG + BRACKET_LEFT_TAG, BRACKET_RIGHT_TAG); if (randTags != null) { for (Iterator it = randTags.iterator(); it.hasNext();) { String key = (String) it.next(); Object value = setEnvironmentList.get(key); // 已存在变量 if (value != null) { commandString = Command .replaceMatch( commandString, (RAND_TAG + BRACKET_LEFT_TAG + key + BRACKET_RIGHT_TAG) .intern(), value.toString()); // 设定有随机数生成范围 } else if (isNumber(key)) { commandString = Command .replaceMatch( commandString, (RAND_TAG + BRACKET_LEFT_TAG + key + BRACKET_RIGHT_TAG) .intern(), String .valueOf(GLOBAL_RAND .nextInt(Integer .parseInt((String) key)))); // 无设定 } else { commandString = Command .replaceMatch( commandString, (RAND_TAG + BRACKET_LEFT_TAG + key + BRACKET_RIGHT_TAG) .intern(), String .valueOf(GLOBAL_RAND.nextInt())); } } } } } /** * 逐行执行脚本命令 * * @return */ public synchronized String doExecute() { executeCommand = null; addCommand = true; isInnerCommand = (innerCommand != null); if_bool = false; elseif_bool = false; try { nowPosFlagName = String.valueOf(offsetPos); int length = conditionEnvironmentList.size(); if (length > 0) { Object ifResult = conditionEnvironmentList.get(length - 1); if (ifResult != null) { backIfBool = ((Boolean) ifResult).booleanValue(); } } // 获得全行命令 commandString = ((String) scriptList.get(offsetPos)); // 清空脚本缓存 if (commandString.startsWith(RESET_CACHE_TAG)) { resetCache(); return executeCommand; } if (isCache) { // 获得缓存命令行名 cacheCommandName = nowCacheOffsetName(); // 读取缓存的脚本 Object cache = cacheScript.get(cacheCommandName); if (cache != null) { return (String) cache; } } // 注释中 if (flaging) { flaging = !(commandString.startsWith(FLAG_LS_E_TAG) || commandString .endsWith(FLAG_LS_E_TAG)); return executeCommand; } if (!flaging) { // 全局注释 if (commandString.startsWith(FLAG_LS_B_TAG) && !commandString.endsWith(FLAG_LS_E_TAG)) { flaging = true; return executeCommand; } else if (commandString.startsWith(FLAG_LS_B_TAG) && commandString.endsWith(FLAG_LS_E_TAG)) { return executeCommand; } } if (backIfBool) { // 加载内部脚本 if (commandString.startsWith(INCLUDE_TAG)) { temps = commandSplit(commandString); String fileName = (String) temps.get(1); if (fileName != null) { innerCommand = new Command(fileName); isInnerCommand = true; offsetPos++; } } // 执行内部脚本 if (isInnerCommand && !isCall) { setVariables(innerCommand.getVariables()); if (innerCommand.next()) { return innerCommand.doExecute(); } else { innerCommand = null; return executeCommand; } } } // 执行随机数标记 setupRandom(); // 执行获取变量标记 setupSET(); // 结束脚本中代码段标记 if (commandString.endsWith(END_TAG)) { functioning = false; return executeCommand; } // 标注脚本中代码段标记 if (commandString.startsWith(BEGIN_TAG)) { temps = commandSplit(commandString); if (temps.size() == 2) { functioning = true; functions.put(temps.get(1), new ArrayList(10)); return executeCommand; } } // 开始记录代码段 if (functioning) { ArrayList function = (ArrayList) functions .get(functions.size() - 1); function.add(commandString); return executeCommand; } // 执行代码段调用标记 if (commandString.startsWith(CALL_TAG) && !isCall) { temps = commandSplit(commandString); if (temps.size() == 2) { String functionName = (String) temps.get(1); List funs = (ArrayList) functions.get(functionName); if (funs != null) { innerCommand = new Command(scriptName + FLAG + functionName, funs); innerCommand.setVariables(getVariables()); innerCommand.closeCache(); isCall = true; isInnerCommand = true; offsetPos++; } } } // 执行call命令 if (isInnerCommand && isCall) { setVariables(innerCommand.getVariables()); if (innerCommand.next()) { return innerCommand.doExecute(); } else { isCall = false; innerCommand = null; return executeCommand; } } if (!if_bool && !elseif_bool) { // 获得循序结构条件 if_bool = commandString.startsWith(IF_TAG); elseif_bool = commandString.startsWith(ELSE_TAG); } // 条件判断a if (if_bool) { esleflag = setupIF(commandString, nowPosFlagName, setEnvironmentList, conditionEnvironmentList); addCommand = false; ifing = true; // 条件判断b } else if (elseif_bool) { String[] value = split(commandString, " "); if (!backIfBool && !esleflag) { // 存在if判断 if (value.length > 1 && IF_TAG.equals(value[1])) { esleflag = setupIF(commandString.replaceAll(ELSE_TAG, "").trim(), nowPosFlagName, setEnvironmentList, conditionEnvironmentList); addCommand = false; } } else { addCommand = false; conditionEnvironmentList.put(nowPosFlagName, new Boolean( false)); } } // 分支结束 if (commandString.startsWith(IF_END_TAG)) { conditionEnvironmentList.clear(); backIfBool = false; addCommand = false; ifing = false; if_bool = false; elseif_bool = false; } // 选择项列表结束 if (commandString.startsWith(OUT_TAG)) { isRead = false; addCommand = false; executeCommand = (SELECTS_TAG + " " + reader.toString()) .intern(); } // 累计选择项 if (isRead) { reader.append(commandString); reader.append(FLAG); addCommand = false; } // 选择项列表 if (commandString.startsWith(IN_TAG)) { reader.delete(0, reader.length()); isRead = true; return executeCommand; } // 输出脚本判断 if (addCommand && ifing) { if (backIfBool && esleflag) { executeCommand = commandString; } } else if (addCommand) { executeCommand = commandString; } // 替换脚本字符串内容 if (executeCommand != null) { printTags = Command.getNameTags(executeCommand, PRINT_TAG + BRACKET_LEFT_TAG, BRACKET_RIGHT_TAG); if (printTags != null) { for (Iterator it = printTags.iterator(); it.hasNext();) { String key = (String) it.next(); Object value = setEnvironmentList.get(key); if (value != null) { executeCommand = Command .replaceMatch( executeCommand, (PRINT_TAG + BRACKET_LEFT_TAG + key + BRACKET_RIGHT_TAG) .intern(), value.toString()); } else { executeCommand = Command .replaceMatch( executeCommand, (PRINT_TAG + BRACKET_LEFT_TAG + key + BRACKET_RIGHT_TAG) .intern(), key); } } } if (isCache) { // 注入脚本缓存 cacheScript.put(cacheCommandName, executeCommand); } } } catch (Exception ex) { throw new RuntimeException(ex); } finally { if (!isInnerCommand) { offsetPos++; } } return executeCommand; } /** * 包含指定脚本内容 * * @param fileName * @return */ private static List includeFile(String fileName) { InputStream in = null; BufferedReader reader = null; List result = new ArrayList(1000); try { in = ResourceLoader.getResourceToInputStream(fileName); reader = new BufferedReader(new InputStreamReader(in, LSystem.encoding)); String record = null; while ((record = reader.readLine()) != null) { record = record.trim(); if (record.length() > 0) { if (!(record.startsWith(FLAG_L_TAG) || record.startsWith(FLAG_C_TAG) || record .startsWith(FLAG_I_TAG))) { result.add(record); } } } } catch (Exception ex) { throw new RuntimeException(ex); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } return result; } /** * 过滤指定脚本文件内容为list * * @param src * @return */ public static List commandSplit(final String src) { String[] cmds; String result = src; result = result.replace("\r", ""); result = (FLAG + result).intern(); result = result.replaceAll(" ", FLAG); result = result.replace("\t", FLAG); result = result.replace("<=", (FLAG + "<=" + FLAG).intern()); result = result.replace(">=", (FLAG + ">=" + FLAG).intern()); result = result.replace("==", (FLAG + "==" + FLAG).intern()); result = result.replace("!=", (FLAG + "!=" + FLAG).intern()); if (result.indexOf("<=") == -1) { result = result.replace("<", (FLAG + "<" + FLAG).intern()); } if (result.indexOf(">=") == -1) { result = result.replace(">", (FLAG + ">" + FLAG).intern()); } result = result.substring(1); cmds = result.split(FLAG); return Arrays.asList(cmds); } /* public static void main(String[] args) { Command command = new Command("script/start.txt"); for (Iterator it = command.batchToList().iterator(); it.hasNext();) { System.out.println(it.next()); } }*/ }

为了与游戏相交互,此示例构建了一个顺序执行的动态解释型脚本语言,其中每一行实际上就是一组独立的命令,无论分支或者输入输出都仅在程序读取到本行时才会活性化, 以此保证与游戏主程序数据交互的实时性。

本例解释的脚本start.txt内容如下:

/* 这是一个随Java版AVG游戏开发入门示例发布的脚本文件,用于演示脚本引擎开发的基本原理及应用 */ set bgImage = "image/avg/backgroud/hospital.jpg" set heroName = "三石.圣光.丁" set cg0Image = "image/avg/friend/ding/ding0.png" set cg1Image = "image/avg/friend/ding/ding1.png" set cg2Image = "image/avg/friend/ding/ding2.png" set cg3Image = "image/avg/friend/ding/ding3.png" set roleName = "那厮为金网瘾集中营女医师—上岛床子" set ecg0Image = "image/avg/enemy/shang/shang0.png" set ecg1Image = "image/avg/enemy/shang/shang1.png" set ecg2Image = "image/avg/enemy/shang/shang2.png" set ecg3Image = "image/avg/enemy/shang/shang3.png" set content = "" set mount = 0 gb print(bgImage) //显示三石.圣光.丁惊讶画面到画面x坐标200 cg print(cg1Image) 200 mes {print(heroName)}你说我有网瘾?!但我已不摸电脑好多年,我,我现在只是个养猪的…… //三石移动到30 cg print(cg1Image) 30 //上岛床子乱入……250位置 cg print(ecg0Image) 250 mes {print(roleName)}哼哼哼哼哼,别狡辩了,根据我们伟大的[<o那厮为金网瘾集中营/>]<r文明用语/>的推断。你的,良民的不是,网瘾的,大大的有! //变更cg0为cg1 cg print(cg1Image) to print(cg2Image) mes {print(heroName)}<o那厮为金网瘾集中营/>的<r文明用语/>?!是什么鬼东西? //变更ecg0为ecg1 cg print(ecg0Image) to print(ecg1Image) mes {print(roleName)}放肆!你竟敢说我们尊贵的<r文明用语/>是东西?! //变更cg2为cg1 cg print(cg2Image) to print(cg1Image) mes {print(heroName)}难道<r文明用语/>还不是东西吗?! //变更ecg1为ecg2 cg print(ecg1Image) to print(ecg2Image) //变更cg1为cg0 cg print(cg1Image) to print(cg0Image) mes {print(roleName)}真是傻孩子!<r文明用语/>怎么可能是东西,<r文明用语/>当然不是东西!更何况是我们营长这个<r文明用语/>,那就更加更加的不是东西! //变更cg0为cg1 cg print(cg0Image) to print(cg1Image) mes {print(heroName)}营长?!你是说,你讲的<r文明用语/>是指你们的营长?! mes {print(roleName)}蠢话!不是我们营长这样身份高贵的人,怎么可能用<r文明用语/>作为尊称呢?<r文明用语/>当然就是指我们杨营长! //变更cg1为cg3 cg print(cg1Image) to print(cg3Image) mes {print(heroName)}姓杨?!我想起来了,莫非是传说中的杨~<r哔/>~<r哔/>~?! mes {print(roleName)}孺子可教也,想不到你年纪轻轻竟也知道他这种身份的大人,要用[<r文明用语/>]、[<r哔/>]、[<r不雅词汇/>]这些[<o象征高贵的辞藻/>]来加以尊称。 //变更cg3为cg2 cg print(cg3Image) to print(cg2Image) mes {print(heroName)}你,你想怎么样?……(可恶,如果是战斗力媲美十大神兽的十二生肖兽之一——[<o羊叫兽/>]亲自介入,就算是我恐怕也劫数难逃了|||) mes {print(roleName)}不想怎么样——毕竟我们也不都是虐待狂,如果我们的网瘾测试你能顺利通过,还是有免于被抓的机会的(哼哼,做梦)。 mes {print(heroName)}见鬼!好,谁怕谁啊,放马过来吧! //变更ecg2为ecg0 cg print(ecg2Image) to print(ecg0Image) mes {print(roleName)}接招吧! cg del cg print(ecg2Image) 20 //显示题目用函数(因为这仅是一个演示用的脚本引擎,故未做传参功能,请自行改进添加) begin issue mes {print(roleName)}print(content) in A.完全没有 B.很少 C.偶尔 D.经常 E.总是 out end //题目1 set content = "你多少次发现你在网上逗留的时间比你原来打算的时间要长?" call issue set mount = SELECT + mount + 1 //题目2 set content = "你有多少次忽视了你的家务而把更多时间花在网上?" call issue set mount = SELECT + mount + 1 //题目3 set content = "你有多少次更喜欢因特网的刺激而不是与你配偶之间的亲密?" call issue set mount = SELECT + mount + 1 //题目4 set content = "你有多少次与网友形成新的朋友关系?" call issue set mount = SELECT + mount + 1 //题目5 set content = "你生活中的其他人有多少次向你抱怨你在网上所花的时间太长?" call issue set mount = SELECT + mount + 1 //题目6 set content = "在你需要做其他事情之前,你有多少次去检查你的电子邮件?" call issue set mount = SELECT + mount + 1 //题目7 set content = "由于因特网的存在,你的工作表现或生产效率有多少次受到影响?" call issue set mount = SELECT + mount + 1 //题目8 set content = "你认为脱离互联网便无法正常工作吗?" call issue set mount = SELECT + mount + 1 //题目9 set content = "当有人问你在网上干些什么时,你有多少次变得好为自己辩护或者变得遮遮掩掩?" call issue set mount = SELECT + mount + 1 //题目10 set content = "你有多少次用因特网的安慰想象来排遣关于你生活的那些烦人考虑?" call issue set mount = SELECT + mount + 1 //题目11 set content = "你有多少次发现你自己期待着再一次上网的时间?" call issue set mount = SELECT + mount + 1 //题目12 set content = "你有多少次担心没有了因特网,生活将会变得烦闷,空虚和无趣?" call issue set mount = SELECT + mount + 1 //题目13 set content = "如果有人在你上网时打扰你,你有多少次厉声说话,叫喊或者表示愤怒?" call issue set mount = SELECT + mount + 1 //题目14 set content = "你有多少次因为深夜上网而睡眠不足?" call issue set mount = SELECT + mount + 1 //题目15 set content = "你有多少次在下网时为因特网而出神,或者幻想自己在网上?" call issue set mount = SELECT + mount + 1 //题目16 set content = "当你在网上时,你有多少次发现你自己在说'就再玩几分钟'?" call issue set mount = SELECT + mount + 1 //题目17 set content = "你有多少次试图减少你花在网上的时间但却失败了?" call issue set mount = SELECT + mount + 1 //题目18 set content = "你有多少次试图隐瞒你在网上所花的时间?" call issue set mount = SELECT + mount + 1 //题目19 set content = "你有多少次选择把更多的时间花在网上而不是和其他人一起外出?" call issue set mount = SELECT + mount + 1 //题目20 set content = "当下网时,你感到沮丧,忧郁或者神经质,而这些情绪一旦回到网上就会无影无踪?" call issue set mount = SELECT + mount + 1 if mount<40 mes 你有时候可能会在网上花较长的时间"冲浪",但尚能控制你对网络的使用,因此你属于[<l轻度网瘾患者/>](切,才仅仅这样嘛,不过不要紧,等我来慢慢玩弄他(=‵′=))。 else if mount<70 mes 由于因特网的存在,你正越来越频繁地遇到各种各样的问题,你应当认真考虑它们对你生活的全部影响,因此你属于[<y中度网瘾患者/>](真遗憾,只能使用抑郁性药物,过两天才能电刑……)。 else if mount<100 cg print(ecg2Image) to print(ecg3Image) mes 你的因特网使用正在给你的生活造成许多严重的问题,你需要现在就去解决它们,因此你属于[<r重度网瘾患者/>]!(哈哈哈哈,能电了,能电了,我要玩死这小子!)。 else if mount==100 cg print(ecg2Image) to print(ecg3Image) mes 来人啊!快把我的整套SM道具……不,是整套抢救用具拿来!我要立刻[<r挽救/>]这个可爱的小家伙!!! endif flash 255,255,255 mes {print(roleName)}你是我的了! shake 20 cg del cg print(cg1Image) mes {print(heroName)}救命啊!<r不要啊/>!!!! flash 241,26,26 cg del mes {路人H}由于这是一个示例,并没有真正完成这个游戏,暂时到此为止。想继续请自己动手丰衣足食……想添加过场特校、按钮、物品栏等组件可以给我blog留言,偶可以抽空帮忙,其余的自己解决…… run title

游戏运行界面如下图:

源码下载地址:http://code.google.com/p/loon-simple/downloads/list

——————————————————————————————————————

天气很热,写不出什么正经东西,由于最近[叫兽]很猖獗,随便做点东西借机踩踩它(昨天晚上写的,因为太热导致MV归0,今天晚上才发出来|||)。

不过这星期六、日两天TLOH基础部分应该也能写完了,下周估计能发。总之是个AVG+RPG+SLG的开源游戏,剧情部分就靠发到网上情大家帮忙添加∩ω∩,功能我会慢慢再补,所有对它的意见反馈最终都会归结到Loonframework-Game这个框架(简称LFG)上去。

Java版AVG游戏开发入门示例[3]——脚本引擎的制作及应用相关推荐

  1. Java版AVG游戏开发入门示例 3 ——脚本引擎的制作及应用

    源码下载地址:http://code.google.com/p/loon-simple/downloads/list 根据wikipedia的解释:脚本语言(Script language,scrip ...

  2. Java版AVG游戏开发入门[0]——游戏模式转换中的事件交互

    示例程序下载地址:http://download.csdn.net/source/999273(源码在jar内) AVG,即Adventure Game,可以直译为[冒险游戏].但是通常情况下我们说A ...

  3. Java版AVG游戏开发入门[1] —— CG的绘制

    作为Adventure Game,AVG的图文部分向来便是整个游戏的核心之一,所以本回将以图像绘制为中心讲解AVG的CG生成问题.(CG,即Computer Graphics,直译可称[计算机图形], ...

  4. Java版SLG游戏开发入门 1 --关于本系列

    离上次更新博客又过了较长的一段时间,其实也没什么正经事,就是一堆闲事闲人闹的.本来这个系列想写一篇文章加一部分代码,但考虑到近期未必能时常更新,所以准备先把完成的用例发上来,等本系列写完后再统一发源码 ...

  5. 基于Java的移动游戏开发入门(来自51CTO论坛)

    一. 引言 近来,移动游戏在提供个人娱乐方面变得越来越流行.这种流行使得移动游戏给手机用户,游戏出版商和手持设备制造者带来丰厚的利润:移动游戏,在给游戏开发者和相关人员带来无数商机的同时,处于极为重要 ...

  6. Java版色情图像过滤入门示例及源码-0 1 0 (模拟绿坝过滤机制)

    文件下载地址(源码在jar中):http://code.google.com/p/greenvm/downloads/list 这些天来,笔者对于[绿坝]的赞美犹如滔滔江水连绵不绝,又似黄河决口,一发 ...

  7. Java版色情图像过滤入门示例及源码-0.1.0 (模拟绿坝过滤机制)

    文件下载地址(源码在jar中):http://code.google.com/p/greenvm/downloads/list 这些天来,笔者对于[绿坝]的赞美犹如滔滔江水连绵不绝,又似黄河决口,一发 ...

  8. Java版色情图像过滤入门示例及源码-0.1.0 (模拟GreenDam过滤机制)

    文件下载地址(源码在jar中):http://code.google.com/p/greenvm/downloads/list 这些天来,笔者对于[绿坝]的赞美犹如滔滔江水连绵不绝,又似黄河决口,一发 ...

  9. java游戏开发入门(十) -粒子特效

    java游戏开发入门十 - 粒子特效 java游戏开发入门十 - 粒子特效 前言 编码 创建一个粒子发射器,并将粒子发射器添加到实体对象 效果图 完整代码 完整项目 java游戏开发入门十 - 粒子特 ...

最新文章

  1. xajax中的中文乱码问题
  2. CodeForces - 1454F Array Partition(线段树+二分)
  3. python新闻聚合_使用python对数据进行高效处理,包你玩转分组聚合
  4. docker 搭建nginx php mysql_Docker搭建PHP/Nginx/MySQL/Composer环境
  5. 201621123003《Java程序设计》第一周学习总结
  6. 求计算两个时间的差(DateTime类和TimeSpan类)
  7. 原linux的字符文件作用,linux特殊字符及其作用大全
  8. 设置版权声明html,在Hexo中自动为Yilia主题增加版权声明
  9. WAP1.X/WAP2.0以及WAP浏览器的协议版本
  10. 几百本计算机类常用书籍
  11. 【深度学习环境配置】手把手实战配置深度学习环境(win10版)
  12. HTML5期末大作业:影视在线网站设计——电影网H5(9页) HTML+CSS+JavaScript 学生DW网页设计作业成品 web课程设计网页规划与设计 计算机毕设网页设计源码
  13. APP地推前要准备些什么?
  14. 湖北文理学院数学与计算机科学学院,以德为本严要求 以勤为先勇创新——记湖北文理学院数学与计算机科学学院执行院长 吴 钊-湖北文理学院校报电子版《湖北文理学院报》...
  15. 我国羽绒制品在欧洲遭遇部分消费者抵制
  16. LVGL的多语言转换工具--MCU_Font_Release
  17. Nginx+mongrel部署Rails应用
  18. 软件测试工程师必备的27个基础技能【快来看看有没有遗忘的】
  19. 事件营销的话题选择之道
  20. Python实现带南海九段线分位数地图完整可视化版本(附代码及数据)

热门文章

  1. 20200202——记特殊的春节假期
  2. 花呗问答匹配(part1 数据预处理)
  3. 唯心主义vs唯物主义
  4. 记录故障解决--ThinkPad. E14 Gen 2开机发出警报
  5. py2exe打包生成exe,图标在win7下面不显示解决方案
  6. python两个不同变量相加_Python之旅2-----变量、运算与编码
  7. Android按钮 button
  8. PPC907BE 3BHE024577R0101用于工业自动化设备
  9. 一文读懂 SAP 全球认证
  10. iPhone手机短信如何迁移到电脑,苹果手机短信同步到电脑