撰写了文章 发布于 2017-04-09 15:09:36
谈谈 AVG 游戏的程序脚本
其实这是一篇灌水文。
几种脚本
命令式的脚本
类似于命令行或 Shell 的语法,基本结构是 命令名 参数1, 参数2, ..., 参数N,代表是 NScripter,下面是它的程序脚本的一段:
*define
clickstr "。",1
game
*start
你好,世界。
effect 3,15,2000,"m3.bmp"
这是我第一次写NScripter脚本。
end
这里要提一下吉里吉里 2(aka kirikiri2 or krkr2),它的 KAG 脚本看上去是这样的:
[wait time=200]
[loadplugin module=wuvorbis.dll]
*start
[startanchor]
[cm]
[rclick enabled=false]
[clickskip enabled=false]
[history output=false enabled=false]
看上去像是标记语言,但实际上它仍然是命令式的语法,至于为什么应该也不难理解:它并没有「声明」什么东西,不过是命令行脚本换了个格式而已。但值得一提的是,这样的设计对人明显更加友好。
对于 AVG 来说,命令式的语法本是一种十分恰当的设计,不像 RPG 等其他类型, AVG 总是有着一套固定的剧情流程的,哪怕有再多的分支也仍然是有限个故事线。命令式语法天然能够清楚地描述「流程」,并且足够通俗易懂。
但这仅仅是对于剧情脚本来说的,NScripter 将全部引擎功能都放到命令行式的语法中,随之而来的是系统功能编写的复杂化。这些脚本不得不引入了程序语言中的分支、循环语句甚至该死的 GOTO 语句。当然,如果只是要做一个常见的标题界面、设置界面或是音乐鉴赏,这可能并不算是困难,顶多是麻烦。那么,来试试做一个滚动条?处理一下各种鼠标键盘事件?不过,这些对于熟练工来说也算不了什么,那么,做个解谜游戏呢?
例如,一个简单的文字历史界面脚本大概是这样的:



(不管你懵没懵,反正我懵了……)
KAG 中引入了 [if][endif] 等结构化语句来解决逻辑问题,但这也使得脚本的命令式特性不再纯粹,换言之,每行脚本的独立性被破坏,取而代之的是以顺序排列或嵌套的「程序段」。大幅增加了学习成本。
或许这还不是最糟糕的情况。更糟糕的是你用它编写系统时的复杂度,如果你不了解这些脚本没有概念,那么想象一下用 Shell 脚本在控制台里写一个模拟窗口,或是用原生 DOM 写一个 SPA,或是用 SDL 造一个窗口。实际情况在事件处理方面或许比这些类比例子要好不少,但感受上可能只有程度的区别。维护的难度也可见一斑,常常听到的事情是,某个制作组的程序因故退出,后补的新程序员看了看前人写的代码,大肆吐槽一番,然后大幅重构了它……
而 NScripter,你甚至要自己造一个死循环并配合 Goto 语句来处理按钮点击事件!
引入更像程序语言的脚本
事实上,KAG 不过是吉里吉里 2 的二等公民,其真正的脚本系统是名为 TJS 的脚本语言。而 NScripter 也终于在其问世 10 年后(2009 年)提供了 lua 脚本支持。
TJS 是其作者发明的专为吉里吉里 2 使用的语言,无论是名字还是长相都很像是 Javascript,不过,考虑到其发布年代,真相可能更接近于是 ActionScript2 的仿制品(反正是 ECMAScript 就对了)。语言特性在当时来看还是很不错的,更重要的是能直接操作底层的图形对象。这让它更像是一个通用的游戏引擎,而非是文字游戏引擎,这也是为什么后来有人用吉里吉里 2 造出了其他功能更强的剧本系统甚至 RPG 系统。
不过令人失望的是,大概是因为太过底层了吧,反而学和用的人更少了。更多的人只拿它实现一些 KAG 难以实现的算法函数供在 KAG 中调用或在必须调用 TJS API 时不得不用,大部分系统功能仍然是靠 KAG 实现。
试试混写?
既然大家都喜欢命令式语法,有时却又不得不写逻辑,导致命令式语法的优雅被破坏,有没有将二者作出区分的方法?BKEngine 的脚本系统或许是个例子。
BKEngine 的脚本系统直接参考了 NScripter 和 TJS/KAG,最后的结果是一个形式上与 TJS/KAG 极为相似,但又明显不同的语法:
- BKSCR/BKPSR(BKPSR 后改名 Bagel)组成二元结构,分别对应 KAG/TJS
- 与吉里吉里 2 不同,二者都是一等公民,并且支持混写
所谓混写看上去是这样的:
*start
[sprite index=100 file='demo.jpg']
#if x > 1
[addto index100 target=basic_layer x=0 y=0]
#else
[addto index100 target=10 x=0 y=0]
#endif
[bgm file='music.ogg']
虽然在学习 KAG 的过程中仍然保留了[if] 等语句,但明显上面的写法看上去更清楚,流程控制与流程内容明显地区分开了。
虽然是 BKEngine 是自家产品,但就算我刻意不去说它的缺点,它的缺点也仍旧是显而易见的:这是一种治标不治本的方法,也依旧无法避免系统编写和维护的困难。
你们西方这一套啊,excited
没错,我要说 Ren'py。
前面提到的几个引擎都本着「围绕一个命令式脚本,再强化它的功能」的原则,无论是将其视为一等公民还是二等公民,他们最终都是想让人们更多地使用命令式脚本。所以出现那些问题也就不足为怪了。
Ren'py 却反其道而行之,他们尝试从 Python 语法开始简化,让它成为一种命令式脚本,但保留了基本的缩进规则、结构化语句…… 总之它看上去是这样的:
style default:
properties gui.text_properties()
language gui.language
style slider:
ysize gui.slider_size
base_bar Frame("gui/slider/horizontal_[prefix_]bar.png", gui.slider_borders, tile=gui.slider_tile)
thumb "gui/slider/horizontal_[prefix_]thumb.png"
(省略部分内容)
screen quick_menu():
## Ensure this appears on top of other screens.
zorder 100
if quick_menu:
hbox:
style_prefix "quick"
xalign 0.5
yalign 1.0
textbutton _("Back") action Rollback()
textbutton _("History") action ShowMenu('history')
textbutton _("Skip") action Skip() alternate Skip(fast=True, confirm=True)
textbutton _("Auto") action Preference("auto-forward", "toggle")
textbutton _("Save") action ShowMenu('save')
textbutton _("Q.Save") action QuickSave()
textbutton _("Q.Load") action QuickLoad()
textbutton _("Prefs") action ShowMenu('preferences')
init python:
config.overlay_screens.append("quick_menu")
default quick_menu = True
style quick_button is default
style quick_button_text is button_text
这种以程序语言为核心的脚本让系统编写变得极为简单。更值得一题的是 Screen 和 Style 两个概念,有着隐约的声明式与模块化的味道。
不过,这个工作并没有做得彻底。不难发现,上面的代码中并不包含任何交互响应(除了跳转到其他 Screen 的 action ),那么如果我要实现「当点击按钮时,画面中某个图片移动」效果,该怎么做呢?呃,内联原生 Python 代码,比如 $ sprite.position = (x, y)。个人理解,在界面编写方面,Ren'py 脚本与 Python 的关系更像是 HTML+CSS 与 DOM API 的关系,一个描述内容和布局,一个处理行为和变化。尽管如此,Ren'py 提供的动态演出能力相较 BKEngine 甚至吉里吉里 2 还是弱了许多,提供的内置动态效果更是不如二者。如果你用它开发解谜等包含复杂 UI 交互的游戏,这一弊端会更加明显。
综合来看,Ren'py 在界面开发上的初始难度是很低的,但牺牲了定制动态效果的可能性也极大限制了动态演出的能力,如果非要添加交互 UI 和动态演出效果,又要引入 Python 作为补充,实际上难度并未降低。不过,它的声明式 UI 定义和半模块化却是相当先进的思想。
总结一下
受限于时代环境,两位老前辈 NScripter 和 Kirikiri2,一直未能摆脱命令行脚本模式的阴影,所以直到今天仍然能在他们身上看到非常原始的程序实现方式,如事件处理死循环、复杂的 Goto 跳转和封装度很低的事件处理。现在已经 2017 年,他们早就完成了历史使命,也早就是时候抛弃他们了。
BKEngine 和 Ren'py 两位后继者,一个选择继续改进命令行式脚本,只为提供更加丰富的功能;一个毅然选择另辟蹊径,却不得不提供比前辈更少的功能和可定制性。
那么,到底有没有一种可能的方式,能或多或少地兼顾开发维护的便捷与功能的丰富呢?
且听下回分(chui)解(niu)………
Shitake 1年前
剧情文本用标记语言
复杂逻辑用其他
万灵药就是坑
Icemic [作者] 1年前
发布