22
May
这只是一个有趣的探索,demo 使用 java 编写。模拟了一个龙与地下城类 RPG 游戏中,在不同的房间内移动的简单游戏场景。
阅读本文前,首先下载使用 Netbeans 6.5 建立的完整项目代码:下载。然后,我会用 UML 图的方式来说明如何在游戏中使用脚本,其中可能还会简介一下游戏中实体对象的建立和管理(不知道值得不值得另外写一篇文章来介绍了)。
I have a dream…
现在要构建一个极为无聊的小世界。说它小,是因为我只打算让它有三个房间,三个房间之间两两互通的门。仅此而已。首先上图:
我们假设左边上面的房间叫 room-1,右边上面的房间叫 room-2,下面的房间叫 room-3。玩家在这三个房间中穿行,当然,不可能是穿墙。人,一定是要走门的。如图。就这么简单的逻辑而已,不用脚本语言也能轻松完成。不过如果希望多来一点拓展性呢?比如,room-3 不允许等级在5级以下玩家进入;room-2 当十级以上玩家进入后就会自动瞬间移动到 room-1;门锁住以后就不能通过,更夸张一点,门锁住以后如果不把锁打坏就不能通过……可能性太多了。不用脚本的情况下,如果要将这么多都实现,是一件非常繁琐的事情。
好吧,让我们来看看那些游戏公司是怎么解决这个问题的。哦,需要说明的是,这里的解决方案仅仅是一个 demo,只用来解释原理。真正的环境中,还要更复杂一点,不过也就是复杂一点点而已。
游戏中实体类如图设计,为了简化期间,没有实体管理器,所以也没有集成自统一的父类。
Ninny 类也就是玩家,有保存当前所在房间的成员变量。Room 类也就是房间,保存有在当前房间的玩家列表。Door 是描述门的类,保存了这个门连接的两个房间的列表。
为了简单期间,demo 中使用了 java 内置的 javascript 作为脚本语言,详情看这里。
本来还想多写一点的,急着出门。算了,反正有代码,大家看看先。有空回头写。重点在 js 目录下的那几个脚本 enterDoor.js,exitDoor.js,enterRoom.js,exitRoom.js。
好,继续!
为了用起来方便,稍稍封装了一下Scripting的代码。大家直接看代码,不说话:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | import java.io.FileNotFoundException; import java.io.FileReader; import java.util.logging.Level; import java.util.logging.Logger; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; public class Script { private final static ScriptEngineManager factory = new ScriptEngineManager(); private final static ScriptEngine engine = factory.getEngineByName("JavaScript"); public void put(String key, Object value) { engine.put(key, value); } public Object get(String key) { return engine.get(key); } public Object eval(String fileName) throws ScriptException { try { return engine.eval(new FileReader(System.getProperty("user.dir", ".") + "/js/" + fileName + ".js")); } catch (FileNotFoundException ex) { Logger.getLogger(Script.class.getName()).log(Level.SEVERE, null, ex); } return null; } } |
从类图上可以看到,Door 和 Room 都实现了 Enterable 接口,两个方法:一个入 enter,一个出 exit。显然,当进入一个 Room 的时候,必须从另外一个房间出来。为了保证这个一致性,所以限定玩家不能穿墙只能走门:
1 2 3 4 5 6 7 | Room r1 = new Room("room-1"); Room r2 = new Room("room-2"); Door d12 = new Door(); d12.add(r1); d12.add(r2); Ninny player = new Ninny("ninny", r1); d12.enter(player); |
在不用脚本的情况下怎么写这个 Door 的 enter 方法呢?首先判断一下 ninny 是不是跟 Door 在同一个房间;获得 ninny 的当前房间后 exit;获得门另一端的房间 enter。就是这么简单。不过考虑到前面说说的那种种可能性,为了拓展让我们来看一看用脚本是如何处理的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // Door 的 enter 方法 public boolean enter(Ninny ninny) { Room currentRoom = ninny.getOwner(); // 玩家在当前门所在的房间 if(!member.containsKey(currentRoom.getName())) { return false; } // 门另一侧的房间 Room nextRoom = getAnotherRoom(currentRoom); try { Script script = new Script(); script.put("room1", currentRoom); script.put("room2", nextRoom); script.put("door", this); script.put("player", ninny); script.eval("enterDoor"); System.out.println("enterDoor: " + script.get("message")); return Boolean.valueOf(script.get("result").toString()); } catch (ScriptException ex) { Logger.getLogger(Door.class.getName()).log(Level.SEVERE, null, ex); return false; } } |
这里实际上 enter 什么也没有做,只是传递了一些数据到 Script,然后执行了 enterDoor 这个脚本:
1 2 3 4 5 6 7 8 9 | importClass(Packages.foobar.Room); importClass(Packages.foobar.Ninny); importClass(Packages.foobar.Door); room1.exit(player); room2.enter(player); var message = player.getName() + " enter from the room " + room1.getName() + " to the romm " + room2.getName() + "!"; var result = true; |
脚本也很简单,只是让玩家推出当前房间,进入下一个房间。如果房间对进入的玩家有等级要求,则只需:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | importClass(Packages.foobar.Room); importClass(Packages.foobar.Ninny); importClass(Packages.foobar.Door); var result = false; var message = ''; if(room2.needLevel >= player.level ) { room1.exit(player); room2.enter(player); message = player.getName() + " enter from the room " + room1.getName() + " to the romm " + room2.getName() + "!"; result = true; } else { message = player.getName() + " can't enter the romm " + room2.getName() + "! Level" + room2.needLevel +" needed!"; } |
在我的例子代码中并没有这部分代码,实际上 Room、Door、Ninny 这几个类都应该从一个父类 Entity 中继承。这个 Entity 有一个 Map
通过 Enterable(可进入),Pickable(可捡起),Attackable(可攻击)等接口,调用对应的脚本来完成真正的游戏逻辑。
其实那神秘的游戏脚本化就是这么简单。本来还想画几个序列图说明一下脚本的调用,实在有些困了。
准备洗洗睡觉。尚未补充完整的内容,全当大家进阶学习吧。
May 22nd, 2009 at 16:07
我觉得,若是java的话,大可不必这么做,有强悍的JRUBY,JPTYON,JPHP之类什么的,名字别在意,瞎扯的..
直接公用java写好的类库,更强大方便,这不是也挺好的吗?
May 23rd, 2009 at 03:08
关键词是ninny~
May 23rd, 2009 at 05:07
路神,兄弟!Jruby、Jytion、或者 Jphp 之类的都需要三方库,Javascript 的 Scripting 是 java 环境自带的,所以 javascript 是比其他更简单的解决方案。
而且我这里有 Script 类,你大可封装其他的 Scripting 脚本语言来嵌入。Just a demo.
May 26th, 2009 at 07:24
关键词是 NEW ninny~