返回信息流假如我在开发一个RPG游戏。游戏系统自然是很重要的一部分,但数值设计更重要。这涉及到各个角色的血量、攻击力、防御力等数值。为了灵活起见,我们希望这些数值可以很方便地调节。最好方便到不用重新编译系统,只要改一个文本文件,就可以修改数值。而且,我们的专业“数值设计师”是玩桌游的高手,设计过万智牌,拿过三国杀冠军,当过龙与地下城的城主,给农场主、小矮人游戏的作者提过意见,但编程经验并不丰富,甚至可以说几乎没有编程经验。
这种情况,就是脚本语言发挥它的特长的时间了。比如,我们可以让数值设计师编辑下面的文件:
```js
// charSetting.js
var Player = Packages.cn.byr.nuanyangyang.jstest.RolePlaying.Player;
hero = new Player();
hero.name = "Warrior";
hero.health = 100;
hero.damage = 30;
hero.armor = 30;
villain = new Player();
villain.name = "Monster";
villain.health = 120;
villain.damage = 40;
villain.armor = 10;
```
先不管第一行。除此之外,后面的只要识字的就能看懂:我们创建了两个玩家,一个叫"Warrior",一个叫"Monster",它们的血量、攻击力、防御力如代码所示。程序员只要简单地告诉数值设计师应该如何编辑这个脚本,数值设计师就可以发挥他的特长,设计一个有挑战性的游戏了。
从程序员的角度看,上述脚本就是一个嵌入在一个大程序里的小程序,而“大程序”和“小程序”之间有这样的一个约定:“脚本执行完以后,全局变量`hero`和`villain`分别存着两个`Player`对象”。那么,“大程序”要做的,就是把“小程序”装载进来,用一个**脚本引擎**来执行它,然后获取那两个全局变量就行了。
下面是一个例子,用Java写的。Java很早就有标准的“Java Scripting”接口。注意,不是JavaScript,而是“给Java语言做Scripting,即在Java语言里嵌入脚本语言的接口”。Java程序员要做的就是创建一个ScriptEngine对象,然后装载这个脚本就行了。
```java
// RolePlaying.java
package cn.byr.nuanyangyang.jstest;
import java.io.BufferedReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
/** 简单的RPG游戏 */
public class RolePlaying {
/** 游戏角色 */
public static class Player {
public String name;
public int health;
public int damage;
public int armor;
public void show() {
System.out.printf("Character: %s\n", name);
System.out.printf(" health = %d\n", health);
System.out.printf(" damage = %d\n", damage);
System.out.printf(" armor = %d\n", armor);
}
public boolean isAlive() {
return health > 0.0;
}
}
public static void main(String[] args) throws Exception {
// 创建一个scripting engine用来解释JavaScript
ScriptEngineManager sem = new ScriptEngineManager();
ScriptEngine engine = sem.getEngineByName("JavaScript");
// 角色的数值在js里配置
try (BufferedReader r = Files.newBufferedReader(Paths.get("rpg", "charSetting.js"))) {
engine.eval(r);
}
// 两个角色分別在全局变量“hero”和“villain”里。
Player hero = (Player) engine.get("hero");
Player villain = (Player) engine.get("villain");
hero.show();
villain.show();
// 让它们战斗
fight(hero, villain);
}
private static void fight(Player player1, Player player2) {
Player attacker = player1, defender = player2;
// 轮流进攻
while (true) {
// 直到一个角色死为止,战斗结束。
if (!player1.isAlive()) {
System.out.printf("%s died.\n", player1.name);
break;
} else if (!player2.isAlive()) {
System.out.printf("%s died.\n", player2.name);
break;
}
// 打印当前状态
System.out.printf("%s:%d, %s:%d\n", player1.name, player1.health, player2.name, player2.health);
// 攻击,计算伤害。简便起见,伤害的计算方法就是damage-armor。
int damageDealt = attacker.damage - defender.armor;
defender.health -= damageDealt;
System.out.printf(" %s hit %s and dealt %d damage\n", attacker.name, defender.name, damageDealt);
// 攻防双方交换
Player tmp = attacker;
attacker = defender;
defender = tmp;
}
}
}
```
把上述java程序存成`cn/byr/nuanyangyang/jstest/RolePlaying.java`,把js脚本存成`rpg/charSetting.js`,编译运行就行了。输出结果应该是:
```
Character: Warrior
health = 100
damage = 30
armor = 30
Character: Monster
health = 120
damage = 40
armor = 10
Warrior:100, Monster:120
Warrior hit Monster and dealt 20 damage
Warrior:100, Monster:100
Monster hit Warrior and dealt 10 damage
Warrior:90, Monster:100
Warrior hit Monster and dealt 20 damage
Warrior:90, Monster:80
Monster hit Warrior and dealt 10 damage
Warrior:80, Monster:80
Warrior hit Monster and dealt 20 damage
Warrior:80, Monster:60
Monster hit Warrior and dealt 10 damage
Warrior:70, Monster:60
Warrior hit Monster and dealt 20 damage
Warrior:70, Monster:40
Monster hit Warrior and dealt 10 damage
Warrior:60, Monster:40
Warrior hit Monster and dealt 20 damage
Warrior:60, Monster:20
Monster hit Warrior and dealt 10 damage
Warrior:50, Monster:20
Warrior hit Monster and dealt 20 damage
Monster died.
```
这个机制很灵活。如果对这个战斗结果不满意,我会修改那个js,把monster的攻击力调成100。保存js,不用重新编译java,重新执行,战斗结果立马就不同了:
```
Character: Warrior
health = 100
damage = 30
armor = 30
Character: Monster
health = 120
damage = 100
armor = 10
Warrior:100, Monster:120
Warrior hit Monster and dealt 20 damage
Warrior:100, Monster:100
Monster hit Warrior and dealt 70 damage
Warrior:30, Monster:100
Warrior hit Monster and dealt 20 damage
Warrior:30, Monster:80
Monster hit Warrior and dealt 70 damage
Warrior died.
```
对于大型游戏来说,游戏甚至不用退出,重新装载脚本,就可以运行了。
现实世界中,除了Java以外,很多人也会试图在C/C++里嵌入脚本。除了JS以外,Lua也是一个适合这种用途的语言。甚至Lua最初就是为数据录入而设计的。而游戏是Lua的最大的应用场景之一。
参考:
语言无关的通用Java Scripting接口: https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/prog_guide/toc.html
Java 1.7开始自带的Nashorn JavaScript引擎: https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/toc.html
这是一条镜像帖。来源:北邮人论坛 / java-script / #72同步于 2016/9/9
该镜像源已超过 30 天没有更新,可能在源站已被删除。
JavaScript机器人发帖
Java Scripting:Java里嵌入脚本语言
nuanyangyang
2016/9/9镜像同步21 回复
订阅后,新回复会通过你的通知中心匿名送达。
9 条回复
【 在 Ncer 的大作中提到: 】
: rpg maker的最新版用的语言已经从ruby改成js了
这是为什么呢?更容易嵌入吗?这个我倒是同意。起码js有标准,也有嵌入式的引擎。
不知道该看什么文档。。。我仔细看了下,发现他第一行和那个java类的内部类好像有什么不可告人的秘密~
【 在 nuanyangyang 的大作中提到: 】
: 乖,读文档去