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

               

源码下载地址: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 protected] * @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 = 0gb print(bgImage) //显示三石.圣光.丁惊讶画面到画面x坐标200 cg print(cg1Image) 200 mes {print(heroName)}你说我有网瘾?!但我已不摸电脑好多年,我,我现在只是个养猪的…… //三石移动到30 cg print(cg1Image) 30 //上岛床子乱入……250位置 cg print(ecg0Image) 250mes {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 delcg print(ecg2Image) 20 //显示题目用函数(因为这仅是一个演示用的脚本引擎,故未做传参功能,请自行改进添加)begin issue    mes {print(roleName)}print(content)    in    A.完全没有    B.很少    C.偶尔    D.经常    E.总是 outend//题目1set content = "你多少次发现你在网上逗留的时间比你原来打算的时间要长?"call issueset mount = SELECT + mount + 1//题目2set content = "你有多少次忽视了你的家务而把更多时间花在网上?"call issueset mount = SELECT + mount + 1//题目3set content = "你有多少次更喜欢因特网的刺激而不是与你配偶之间的亲密?"call issueset mount = SELECT + mount + 1//题目4set content = "你有多少次与网友形成新的朋友关系?"call issueset mount = SELECT + mount + 1//题目5set content = "你生活中的其他人有多少次向你抱怨你在网上所花的时间太长?"call issueset mount = SELECT + mount + 1//题目6set content = "在你需要做其他事情之前,你有多少次去检查你的电子邮件?"call issueset mount = SELECT + mount + 1//题目7set content = "由于因特网的存在,你的工作表现或生产效率有多少次受到影响?"call issueset mount = SELECT + mount + 1//题目8set content = "你认为脱离互联网便无法正常工作吗?"call issueset mount = SELECT + mount + 1//题目9set content = "当有人问你在网上干些什么时,你有多少次变得好为自己辩护或者变得遮遮掩掩?"call issueset mount = SELECT + mount + 1//题目10set content = "你有多少次用因特网的安慰想象来排遣关于你生活的那些烦人考虑?"call issueset mount = SELECT + mount + 1//题目11set content = "你有多少次发现你自己期待着再一次上网的时间?"call issueset mount = SELECT + mount + 1//题目12set content = "你有多少次担心没有了因特网,生活将会变得烦闷,空虚和无趣?"call issueset mount = SELECT + mount + 1//题目13set content = "如果有人在你上网时打扰你,你有多少次厉声说话,叫喊或者表示愤怒?"call issueset mount = SELECT + mount + 1//题目14set content = "你有多少次因为深夜上网而睡眠不足?"call issueset mount = SELECT + mount + 1//题目15set content = "你有多少次在下网时为因特网而出神,或者幻想自己在网上?"call issueset mount = SELECT + mount + 1//题目16set content = "当你在网上时,你有多少次发现你自己在说'就再玩几分钟'?"call issueset mount = SELECT + mount + 1//题目17set content = "你有多少次试图减少你花在网上的时间但却失败了?"call issueset mount = SELECT + mount + 1//题目18set content = "你有多少次试图隐瞒你在网上所花的时间?"call issueset mount = SELECT + mount + 1//题目19set content = "你有多少次选择把更多的时间花在网上而不是和其他人一起外出?"call issueset mount = SELECT + mount + 1//题目20set content = "当下网时,你感到沮丧,忧郁或者神经质,而这些情绪一旦回到网上就会无影无踪?"call issueset mount = SELECT + mount + 1if 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挽救/>]这个可爱的小家伙!!! endifflash 255,255,255 mes  {print(roleName)}你是我的了! shake 20  cg del  cg print(cg1Image) mes  {print(heroName)}救命啊!<r不要啊/>!!!!flash 241,26,26cg delmes  {路人H}由于这是一个示例,并没有真正完成这个游戏,暂时到此为止。想继续请自己动手丰衣足食……想添加过场特校、按钮、物品栏等组件可以给我blog留言,偶可以抽空帮忙,其余的自己解决……run title

 

游戏运行界面如下图:

 

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

 

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

 

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

 

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

 

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

 

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


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

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

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

           

再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow