Game脚本语言比较
这些东西是平时遇到的, 觉得有一定的价值, 所以记录下来, 以后遇到类似的问题可以查阅, 同时分享出来也能方便需要的人, 转载请注明来自RingOfTheC[ring.of.the.c@gmail.com]
发现一篇很好的比较常见脚本语言的文章, 翻译过来 🙂
我很熟悉Lua, 也了解一些GameMonkey, 最近正在翻译GameMonkey的资料, 对于本文中其他的语言, 都只是停留在知道这样的层面, 翻译中可能有一些不当的地方, 先翻出来再说吧
原文地址是: http://codeplea.com/game-scripting-languages
Game Scripting Languages
概述
许多游戏都使用脚本来制作动画和游戏玩法逻辑. 脚本语言通常都擅长于快速原型, 以及提供更好的代码组织结构. 几乎每个有用的游戏引擎都会使用某种脚本语言.
有很多有用的脚本语言能完成工作. 其中Lua可能是最为流行的游戏脚本语言. 其他选择有: AngelScript, GameMonkey, Io, Pawn, Squirrel以及Scheme. 还有一些重量级选手可以选择, 比如Python, Ruby, 当然这些语言往往不易现实嵌入, 其次它们在速度上值得商榷.
在这里我将比较AngelScript, GameMonkey, Pawn, Lua以及Squirrel. 也会简单介绍一下TinyScheme, 但并不把它和其他语言运行做比较. Io看起来很有趣, 但是它文档太过匮乏, 我也不打算在这里比较它.
语言介绍
Pawn在这群语言中是个古怪的家伙, 我从它开始介绍
Pawn 小, 而且简单, 它使用C风格的语法, 而且只有一种数据结构, 叫做cell. 一个cell通常是一个integer, 但是它经过处理后也可以表示character, boolean, float. Pawn不支持struct 和class, 但是使用数组的”named position”来模拟struct.
在这篇文章中提到的所有语言中, Pawn是唯一一个将编译器和虚拟机完全分离的脚本. 当然, 它在编译期提供更多的检查. 所有的变量必须声明, 所有的本地函数必须前向声明. 这很好, 因为这些检查是在compile-time进行的, 避免了在run-time时对本地函数参数的检查. 我发现编译器也给出了考虑周到的警告信息, 比如它会警告你使用了不正确的缩进, 这样可以帮助你维持一致的编程风格, 防止意外错误. 对于它, 我唯一抱怨的地方就是, 在使用float类型常量的时候, 你必须写成0.5形式, 而不能是.5这个被认为是语法错误.
Pawn有完整的文档和广泛的应用, 以及一个不太活跃的论坛. Pawn是我见过的最快的轻量级脚本语言. 我在游戏中使用Pawn来进行低级脚本编程, 包括角色和对象的动画.
Lua拥有自己独特的语法, 有点像Basic. 它也很快, 编译出的byte-code相当小. Lua是动态语言, 变量在使用之前不需要声明, 而且function作为first-class值(这意味着它的编译器和抽象机制是紧密结合在一起的, function可以保存在一个变量中). Lua中的table被广泛应用, 它是Lua中唯一的复杂数据类型. 一个table中可以部分存储function, 另一部分存储data, 这样就可以用来模仿class, object.
Lua广泛的应用在工业中, 它拥有一个活跃的在线社区和一个巨大的开源组件基地. Lua是本文提及的所有脚本语言中最早产生的, 而且深深地影响着本文中的其他语言, 我发现熟悉Lua的用法之前(这需要大量的联系), lua API document是个难以理解的东西. Lua也有一本书可以用来学习—Programming in Lua, 这本书覆盖了这本语言方方面面的细节.
GameMonkey借鉴了很多Lua中的概念, 不同的是它采用C风格脚本. 它使得table变的非常强大, 可以像Lua那样用来模拟object. 它提供有限状态机机制, 变量使用前不需要声明, function也是作为first-class.
GameMonkey的速度和byte-code的小巧着实让我惊讶(我的意思是它是至精至简的). GM的API参考文档相当的匮乏. 源代码是使用Doxygen结构注释的, 所以可以使用Doxygen来生产帮助文档(我并没有发现生成好的online帮助文档). 它有一个远程脚本调试器. 我只是短暂的玩了一下这个调试器, 但是还是发现它是相当简洁的.
看不到有关于GameMonkey的天花乱坠的宣传, 但是它有一个活跃的论坛. 几个社区成员在背后推进着GameMonkey的发展, 主要是提供各种bindings, 以及更好的调试器等.
Squirrel是一个高级动态类型, 面向对象的语言, 提供了类和继承, 使用C风格. 同样, 它借鉴了Lua中的table. 它很容易编译, 而且看起来有高水平的文档.
尽管Squirrel是一门年轻的语言, 但是它以及被用在了一些商业程序中, 它有一个活跃的论坛.
AngelScript是一门静态类型语言, C++风格. AngelScript拥有最好的原生支持bindings. 通常, 一个函数或者是类只需要注册到AS的虚拟机中, 就可以在AS脚本中使用了. 本文中的其他脚本语言都需要中间插件帮助才能实现函数的binding. 当然, 如果本地函数没有先注册的话, AS脚本也是编不过的. 这给那些想要预编译AS byte-code的人增加了一个额外步骤.
AngelScript不支持table, 事实上这一点不会造成麻烦, 因为AS是静态类型脚本.
AS拥有一个活跃的在线论坛, 漂亮的文档, 而且保持持续的更新.
TinyScheme有被拿来讨论的价值. 它包含在一个C的源文件中. 不像本文中提到的其他脚本语言, 它是解释性的, 所以它的速度很慢, 当然, 如果速度问题对你没有太大影响, 而且你刚好想将Scheme加入到你的项目中去, 我强烈推荐TinyScheme.
我考验过很多其他的Scheme, 但是都碰到了严重的编译问题. TinyScheme很容易编译, 而且它有很多选项可以供你hack.
测试用的版本和授权
语言 | 版本 | 授权 |
AngelScript | 2.16.0 | zlib |
GameMonkey | 1.25 | MIT |
Lua | 5.1.4 | MIT |
Pawn | 3.3.4058 | zlib |
Squirrel | 2.2.2 | zlib |
TinyScheme | 1.39 | BSD |
绑定
如果嵌入式脚本语言不能调用本地C函数或者是C++方法, 作用就会大打折扣. AngelScript的binding非常简单, 只要你告诉lib你的函数, 就可以马上在脚本中使用了. 其他脚本都需要一些额外的粘合代码.
Pawn把一个int型的数组作为传给函数的参数. float需要简单的转换, 对于string来说你需要调用一些特殊的函数.
GameMonkey, Lua, Squirrel使用一个栈来传递参数. 因为他们都是动态类型语言, 值可以以不同的类型从栈中弹出(比如: int, float, char*). 这个方法有一点不好理解, 但是它可以工作的很好. 每个语言都定义了一些宏, 使得对栈的操作更加轻松容易.
每个库需要第三方库的支持, 以便使得binding更加容易. 比如, 这是一份Lua可用的binding库. 个人认为, 你可以使用本地binding很好的工作, 它们通常是一些宏.
并发
AngelScript, Lua, GameMonkey和Squirrel都支持某种形式的并发. 这允许脚本创建让虚拟机看起来是在并发的thread. 这样可以避免在执行一个很长的算法时扰乱职责. 这些thread不会提供性能上的优势, 因为它们都是用户线程, 但是他们提供了程序设计上的优势, 那就是产生一个单独的执行流去执行一个处理过程.
GameMonkey原生的提供让thread阻塞在某个事件上只到该事件发生. 比如说, 如果一个thread需要等待一个”door open”事件, 它可以在此之前去睡眠, 只到某个线程抛出”door open”事件. 我认为要给其他的脚本加上类似的机制不是一件困难的事情, 但是GameMonkey已经提供了这样的机制.
速度
我对好几种语言进行了速度测试, 这些测试不能够反映出真实应用中的情况, 但是他们给出了一个各种语言之间的比较. Fibonacci数列测试, 脚本函数递归调用1,402,817,464次. Primi测试主要是反复的迭代和基本的integer运算. 原生的string测试是脚本中调用本地C/C++函数10兆次, 传递12个字符的字符串参数. 原生数字测试调用本地函数billion次, 传递一个单独的数字参数. Lua只能支持double, 但是其他语言使用integer.
每个测试, 都先将脚本编译成为byte-code, 因此测试时间包括从磁盘上载入byte-code, 但是它不包含脚本的编译时间. Pawn, Lua, Squirrel使用独立的编译器. AS和GM使用的byte-code但没有独立的编译器, 但是我不得不去汇集程序产生的byte-code.
一个值得提到的信息是Lua, Pawn, Squirrel都有自己的Just-In-Time编译器可以. 我没有测试它们. Pawn附带着一个预编译的实现, 所以我测试了它.
自从脚本语言被经常用于字符串处理以来, 我就在基准测试中向本地函数传递变长字符串. Pawn在它的虚拟机中有一种独特的表现字符串的方式, 转化一个长的字符串的代价很大. 在其他语言中不需要做什么特别的转换, 所以速度和字符串的长度没有什么关系.
Byte-Code大小
在发布游戏的时候, 往往不会直接发布游戏的脚本源码, 而是发布编译后的byte-code. 每个测试的脚本语言都产生了较小的byte-code, 它们很适合用来当着配置脚本. 在很多情况下, 在大小和速度上, 这些脚本语言都要优于传统的配置文件, 比如XML.
库大小
这个可能很少会是一个问题, 其实就是指的虚拟机库大小. 换句话说, 如果你想你的程序运行脚本, 你可以通过库大小预期你的程序的膨胀程度.
语法
我将进行了基准测试的脚本的源码提供给你, 通过这些代码就可以给你一个对各种脚本语言语法的基本感受.
AngelScript, GameMonkey, Pawn和Squirrel使用C风格, Lua拥有自己的类Basic风格, 这种风格继承了Basic亲近初学者的传统, 使得Lua对非程序员更加容易接近.
Scheme的语法和其他的有一些区别, 但是如果你知道C, 你应该可以在10分钟之内掌握其他任何一门语言语法的90%的内容.
结束语
每个语言都有其长处和短处, 如果你需要一门嵌入式的脚本语言, 我希望这篇文章能给你一定的帮助.
我尽可能的保持公平, 但是我不确定自己有没有在这个问题上犯错误, 欢迎任何的评论/建议/纠正.