《LifeLine》这个文字冒险游戏确实挺新颖的,当然,是对我们这一群习惯了GUI和CG的人来说。以前也有过类似形式的游戏,比如,逗B编程小王子就说,他们公司以前写过一个“短信游戏”,你发短信,告诉主人公下一步该怎么走怎么走……一条短信一块钱。
一块钱买了这个游戏,三天通关。在玩通关了之后,心血来潮:这东西的原理一定很简单!游戏肯定是以脚本方式工作的!脚本肯定是存在一个TXT里面或者数据库中!为啥?直觉。要相信一个程序猿的直觉。
那好,自己写一个出来玩玩。
拿到游戏脚本
由于下载不到ipa文件(iOS专用),只好转战Android平台,下载了apk。解压缩,在assets
文件夹中找到了StoryData_en.txt
等文件,打开看了以下,恩,确实是我们需要的游戏脚本。(点击查看 非官方中文版游戏脚本)
iOS版有官方简体中文版,Android只有德文、英文、法语、日语、俄语,并没有中文。当然,国内汉化组很多……比对了国内汉化组和官方中文版,惊叹有些时候山寨的水平竟然能高于原著。
分析游戏脚本
脚本拿到了,就可以开启“上帝模式”了。本来想着是“只需要跳转跳转再跳转”就好了,应该很简单吧?结果发现,并没有第一想法那样简单。脚本里面使用了很多自定义语法,可能还得自己写一个脚本解释器才能让它工作。(后来才知道,这东西叫做“模板引擎”,和各种框架的V层异曲同工)
大致看了一下游戏脚本,里面的语法不太难,标签元素不太多,大致分为以下几种:
- 游戏区块定节类:
:: BlockName
,其中BlockName
是区块的名字。整个游戏是一个区块一个区块进行的,区块之间有跳转,每个区块有自己的名字。区块没有结束标志。 -
跳转类:
[[BlockName]]
,其中BlockName
是需要跳转到的区块的名字。 -
延时跳转类:
[[delay XXX|BlockName]]
,其中XXX
是需要延时的时间,单位为h
(小时)、m
(分钟)、s
(秒)。比如,在出现“我们4小时后见”,后面会有一句[[delay 4h|SomeBlock]]
。使用延时跳转后,会先出现一句“泰勒繁忙”,然后开始延时,游戏暂停,延时后再进行跳转,以正常速度进行游戏。 -
交互选择类:
<<choice [[foo_1|bar_1]]>> | <<choice [[foo_2|bar_2]]>>
。其中foo
是提示语句,bar
是跳转到的区块的名称。由于游戏中都是“二选一”,所以都按照二选一来处理就好了~记得中间有一个|
哦~ -
屏幕显示类:
[SomeSentence]
,其中SomeSentence
都是英文,例如显示“泰勒繁忙”之类的提示语。这些提示语做了国际化处理,对应的翻译在另外的xml文件中。 -
变量赋值类:
<<set $foo = bar>>
,其中foo
是变量,变量前要加上$
,bar
是值。如果值为string,外面需要加上引号。这点和大多数编程语言都一样。 -
变量输出类:
<<$foo>>
,其中foo
是变量。 -
判断类:
<<if >>
、<<elseif >>
、<<else >>
、<<endif >>
,都能看懂吧?判断条都是$var op value
的形式,例如$medicine gte 3
(药品数量大于3片)。op
我发现的有is
(等于)、eq
(等于)、gte
(大于等于)。 -
静默类:
<<silently>> <<endsilently>>
,好象是为了防止输出用的吧?被这一队标签包裹的内容都不会产生输出。 -
注释:
// Comment
。都懂吧……正如游戏Script开头所说If you’re reading this, you’re in for spoilers but you probably don’t care! Thanks for playing, and have fun.
你开心就好。
恩就这么多了。
实现
读脚本文件并进行标准化
大致思路如下:
- 去掉注释
- 去掉空行
- 去掉
Choice
__之间__的|
- 将每一个标签元素都自成一段
解析标签
由于每一个标签都是独立的一行,所以解析起来很简单了:大多数情况下只需要判断标签的前面半部分是什么就好了。
对于跳转什么的,可以拿Map,一下子就实现了,很好用,但是if、else、set这些东西呢?我的做法是将它们拿正则表达式或者纯的strReplace替换成Python的语法,然后使用eval(SomeStatement)
获取结果。然后发现,if好像有嵌套……那就拿一个简单的栈来玩吧。
比如,处理if
:
def __do_if():
# 全局变量表
parameter = self.__parameter
# 标签对我来说并没有用,扔掉
script = script.replace('<<if', '')
script = script.replace('<<elseif', '')
script = script.replace('>>', '')
# 将is、eq、gte等东西替换成Python的
script = script.replace(' is ', ' == ')
script = script.replace(' eq ', ' == ')
script = script.replace(' gte ', ' >= ')
# 用正则表达式,把模板里面带 $ 的变量替换为全局变量表里面的变量
script = re.sub(r'\$(\S+)', r'parameter["\1"]', script)
# 为了防止缩进,做一下Strip
script = script.strip()
# eval一下,拿到判断结果
judgeResult = eval(script)
return judgeResult
延时跳转和本地化我没有做……一是当时没有发现(以为xml文件没有用,直接删掉了),二是,我懒……
但是为了获得一些类似的体验,我设置了几个“延时时间”,最起码能让哪些语句“一条一条地出现”而不是“哗啦一下子全部出现”。延时怎么做?time.sleep(SomeSeconds)
,注意单位是Seconds。
半成品
半成品我已经放到Github上好久了,请打开任意门~。
运行python3 main.py
即可。一定是Python3啊~
啥?GUI?要什么自行车啊!
总结
模板引擎,简单实现嘛,又不用考虑效率神马的。
好像 3 Minute Games 并没有说不允许对它的游戏做逆向工程啊~更何况是“仅作为学习和研究使用”。
实现这个东西大概花了我两个下午和三个晚上。不过呢,人家可是公司啊!美工、剧本……还要求不能出BUG……点个暂顺手物质上支持一下也是应该的。
参考资料
- Python基础教程. [挪]Magnus Lie Hetland 著,司维 等译. 人民邮电出版社. 2014年6月
发表回复