这只是一个有趣的探索,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的代码。大家直接看代码,不说话:
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 的时候,必须从另外一个房间出来。为了保证这个一致性,所以限定玩家不能穿墙只能走门:
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。就是这么简单。不过考虑到前面说说的那种种可能性,为了拓展让我们来看一看用脚本是如何处理的:
// 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 这个脚本:
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;
脚本也很简单,只是让玩家推出当前房间,进入下一个房间。如果房间对进入的玩家有等级要求,则只需:
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(可攻击)等接口,调用对应的脚本来完成真正的游戏逻辑。
其实那神秘的游戏脚本化就是这么简单。本来还想画几个序列图说明一下脚本的调用,实在有些困了。
准备洗洗睡觉。尚未补充完整的内容,全当大家进阶学习吧。
Leave a Reply