2009年2月1日星期日

Nebula3脚本系统

Nebula2的脚本系统实现了c++类的一个脚本接口,脚本命令直接映射到c++方法上。从技术角度上看这是一个不错的想法,但最后,对于脚本系统的主要使用者关卡设计师来说Nebula2的脚本系统太底层和不友好了:关卡设计师使用脚本编写游戏逻辑和行为。

关卡逻辑脚本通常在比c++类接口更高级的层面上编写。直接把脚本命令映射到c++方法上给脚本带来复杂度。bug可能比相同的c++代码还多,因为脚本语言通常是弱类型的并且无法在编译的时候检查错误,因此c++在编译时能检查出来的错误脚本却要在运行时才会发现。这是我们从Project Nomads项目中使用Nebula2脚本系统学习到的经验。

从中我们学到:让你的脚本处于合理的抽象层上。把c++接口映射到脚本语言上不是一个好的想法。

新的Nebula3脚本设计哲学是为关卡设计师在“合理的抽象层”上提供脚本支持。当然了“合理的抽象层”是很难定义的,因此必须在灵活性和易用性之间保持平衡。

除了太底层外,Nebula2脚本系统还有其它一些技术问题:

1.c++方法必须遵守脚本的约定(参数只允许使用简单的数据类型)
2.每个c++方法为了能脚本化需要额外脚本接口代码(在每个方法前增加几行代码)
3.只有从nRoot继承下来的类才可以脚本化
4.对象的持久化和脚本系统耦合在一起(因增加了依赖性使得重构变得更困难)。

现在Nebula3底层的脚本系统看起如何:


1.脚本系统的基础是Scripting::Command类。
2.Scripting::Command完全不依赖任何脚本语言,并由一个名称,和一些输入输出参数组成的。
3.一个新的脚本命令将继承Scripting::Command,并在OnExecute()方法中实现新的脚本命令的功能。
4.脚本命令在使用之前必须注册到ScriptServer。
5.在脚本子系统中ScriptServer是唯一一个和特定脚本语言相关的类,它用来注册新的脚本命令并转化参数。

这个设计比Nebula2简单并且最重要的是它没有和Nebula3的其它部分交织在一起。甚至只要简单改变#define就可以编译一个没有脚本支持的Nebula3。

当然了,使用c++写脚本命令还是像在Nebula2中那样繁琐。这就是NIDL的由来。NIDL的全称是“Nebula Interface Definition Language”。这个设计是为了减少重复劳动,尽量使用简单的XML来定义脚本命令然后把XML编译成实现Sceripting::Command子类的c++代码。

脚本命令有些重要的信息:

1.命令的名称
2.输入参数的类型和名称
3.输出参数的类型和名称
4.实际c++代码

不是很重要但很方便的信息:

1.为实时帮助系统提供关于该命令是做什么的说明和每个参数说明。
2.提供唯一FourCC编码。

大部分脚本命令转换成大约7行的XML-NIDL-code。用一个叫“nidlc”的NIDL编译工具把XML文件编译成c++代码。这个预先处理过程完全集成在VisualStudio中,因此程序员不会因为使用NIDL文件而引发更多的争吵。

为减少文件混乱,把一些相关的脚本命令整合在一起组成脚本库。一个脚本库对应一个NIDL-XML-file文件,并转化成一个c++头文件和c++源文件。脚本库会在程序启动的时候通过script server注册脚本命令,如果你的应用程序不需要或者不想通过脚本读取文件,那么你就不需要注册IO脚本库。这将减小可执行文件的大小,因为如果没有引用连接器将完全丢弃脚本库的c++代码。

最后,Nebula3放弃是使用TCL作为标准的脚本语言,并采用了具有很小运行时的LUA。LUA已经变成游戏脚本事实上的标准,因此比较容易找到已经熟练掌握LUA编程的关卡设计员。

原文: The Nebula3 Scripting System

[声明]:限于译者水平,文中难免错漏之处,欢迎各位网友批评指正;

没有评论:

发表评论