自己写一个 LifeLine


发布于

|

分类

,

LifeLine》这个文字冒险游戏确实挺新颖的,当然,是对我们这一群习惯了 GUI 和 CG 的人来说。以前也有过类似形式的游戏,比如,逗 B 编程小王子就说,他们公司以前写过一个 “短信游戏”,你发短信,告诉主人公下一步该怎么走怎么走……一条短信一块钱。

一块钱买了这个游戏,三天通关。在玩通关了之后,心血来潮:这东西的原理一定很简单!游戏肯定是以脚本方式工作的!脚本肯定是存在一个 TXT 里面或者数据库中!为啥?直觉。要相信一个程序猿的直觉。

那好,自己写一个出来玩玩。

拿到游戏脚本

由于下载不到 ipa 文件(iOS 专用),只好转战 Android 平台,下载了 apk。解压缩,在 assets 文件夹中找到了 StoryData_en.txt 等文件,打开看了以下,恩,确实是我们需要的游戏脚本。(点击查看 非官方中文版游戏脚本

iOS 版有官方简体中文版,Android 只有德文、英文、法语、日语、俄语,并没有中文。当然,国内汉化组很多……比对了国内汉化组和官方中文版,惊叹有些时候山寨的水平竟然能高于原著。

分析游戏脚本

脚本拿到了,就可以开启 “上帝模式” 了。本来想着是 “只需要跳转跳转再跳转” 就好了,应该很简单吧?结果发现,并没有第一想法那样简单。脚本里面使用了很多自定义语法,可能还得自己写一个脚本解释器才能让它工作。(后来才知道,这东西叫做 “模板引擎”,和各种框架的 V 层异曲同工)

大致看了一下游戏脚本,里面的语法不太难,标签元素不太多,大致分为以下几种:

  1. 游戏区块定节类::: BlockName,其中 BlockName 是区块的名字。整个游戏是一个区块一个区块进行的,区块之间有跳转,每个区块有自己的名字。区块没有结束标志。

  2. 跳转类:[[BlockName]],其中 BlockName 是需要跳转到的区块的名字。

  3. 延时跳转类:[[delay XXX|BlockName]],其中 XXX 是需要延时的时间,单位为 h(小时)、m(分钟)、s(秒)。比如,在出现 “我们 4 小时后见”,后面会有一句 [[delay 4h|SomeBlock]]。使用延时跳转后,会先出现一句 “泰勒繁忙”,然后开始延时,游戏暂停,延时后再进行跳转,以正常速度进行游戏。

  4. 交互选择类:<<choice [[foo_1|bar_1]]>> | <<choice [[foo_2|bar_2]]>>。其中 foo 是提示语句,bar 是跳转到的区块的名称。由于游戏中都是 “二选一”,所以都按照二选一来处理就好了~记得中间有一个|哦~

  5. 屏幕显示类:[SomeSentence],其中 SomeSentence 都是英文,例如显示 “泰勒繁忙” 之类的提示语。这些提示语做了国际化处理,对应的翻译在另外的 xml 文件中。

  6. 变量赋值类:<<set $foo = bar>>,其中 foo 是变量,变量前要加上 $bar 是值。如果值为 string,外面需要加上引号。这点和大多数编程语言都一样。

  7. 变量输出类:<<$foo>>,其中 foo 是变量。

  8. 判断类:<<if >><<elseif >><<else >><<endif >>,都能看懂吧?判断条都是 $var op value 的形式,例如 $medicine gte 3(药品数量大于 3 片)。op 我发现的有 is(等于)、eq(等于)、gte(大于等于)。

  9. 静默类:<<silently>> <<endsilently>>,好象是为了防止输出用的吧?被这一队标签包裹的内容都不会产生输出。

  10. 注释:// Comment。都懂吧……正如游戏 Script 开头所说

    If you’re reading this, you’re in for spoilers but you probably don’t care! Thanks for playing, and have fun.

    你开心就好。

恩就这么多了。

实现

读脚本文件并进行标准化

大致思路如下:

  1. 去掉注释
  2. 去掉空行
  3. 去掉 Choice__之间__的|
  4. 将每一个标签元素都自成一段

解析标签

由于每一个标签都是独立的一行,所以解析起来很简单了:大多数情况下只需要判断标签的前面半部分是什么就好了。

对于跳转什么的,可以拿 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……点个暂顺手物质上支持一下也是应该的。

参考资料

  1. Python 基础教程. [挪]Magnus Lie Hetland 著,司维 等译. 人民邮电出版社. 2014 年 6 月

评论

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注