你对自己玩的游戏研究到了什么程度?

本人玩游戏只能算是一个脱离菜鸟也没有达到高手水平的中间水平玩家(个人评价 或许还达不到) 在斗鱼上看到高手大神游戏玩的得心应手 我也相信他们有天分有努力有认真的研究游戏 我只想知道各位高手玩家高手在各自的游戏 (魔兽 飞车 火线 Dota 其它网游页游手游也好)究竟研究到了什么程度 给我这个迷茫的人以方向 推荐书也行 私信我也行 第一次问问题 求给力 小弟拜托了
关注者
3890
被浏览
127859
咦,这帖的galgame玩家还不够多啊。俺来抛砖引玉好了。
TL;DR:大学玩galgame时我有过对游戏痴迷的研究,而那些研究的成果现在让我从事着Java虚拟机的JIT编译器的研发工作(なにぃぃぃぃぃ…??

例如说以前发过这么一帖:[原创][智代アフタ~It's a Wonderful Life~][FX游戏感想+简单攻略][更新歌词]|【Key主题学部】
那是我在智代After刚出的时候就立刻超高速全线通关,憋足干劲把心里的话都掏出来写的。其中的攻略部分,我是如何确认我写的肯定是对的呢?当然我写的时候那些组合我都实际试过了,但最终的确认还是靠把游戏的脚本导出之后分析脚本代码来确认的。不看代码不放心啊(逃

刚看了下,惊讶/惊喜的发现我在GGM开的天坑讨论区还能访问:GALGAME系统引擎研究专区 - GalGames Mix Interactive Community。天啊,感觉是不是得回去更新些帖子了(逃

答得可能有点跑题,不过这毕竟也是玩游戏的结果之一,写出来搏大家一笑。
要贴近点主题的话:题主可能会感兴趣的东西用“speedrun”关键字或许能搜出不少。有很多神乎其神的微操hack,真可谓是把游戏研究得比原作者还精通了orz

=====================================================

(废话一下:在那之前我也痴迷过很多东西,包括别的游戏,例如飞行模拟类、赛车模拟类、即时战略类、机战类。
90年代EA Jane's在国内出过正版的军事类游戏我全买了,NovaLogic的也是。Falcon 4.0 SP3我也玩了很久,Flanker系列和LO:MAC也是。Aero Dancing 4 / Aero Elite我也很喜欢玩。
当然,它们的说明书我都熟读了,对它们现实中的对应物也有不少了解。小时候在航展上参观过F-16C和F/A-18C实机的驾驶舱,相当震撼。还记得小时候抱着Jane's Longbow 2、F-15E那厚厚的说明书是我蹲坑的最爱。
Westwood系的RTS我也没少乱改rules.ini和arts.ini来自制只有我自己玩的奇葩版本。像是把敌方势力的电站的发电量配置成负的这种事情多好玩啊。)

=====================================================

言归正传,回到galgame的主题。


从galgame的脚本语言到JVM研发

我大一开始真的接触galgame,一玩上就停不下来了。一开始我也是从剧情、画面、配音、音乐,偶尔也从游戏性(还有实用性,咳咳)来体验这些游戏的,试着从这些方面入手去考证游戏内容、体会游戏所传达的情感。当时也经常上各种相关主题论坛,拜读前辈们的分析文和感想文,学习到了不少经验。

但我是学软件工程的,玩这些看起来实现简单的游戏难免心里痒痒的,想去了解它们是怎么实现的,自己能否也去实习一个游戏或者游戏引擎,参与到制作游戏的大军当中。

于是我就开始了探索。虽然一开始玩的galgame里有跑在RealLive上的AIR SE版、Kanon SE版、CLANNAD,有跑在rUGP上的君望和日在校园,有跑在KID自家引擎上的MO系列和Ever17,有跑在BGI上的八月社各种游戏…但这些引擎当时既不开源,也没有公开的开发套件(SDK);游戏虽好,想从它们开始着手学习却深感无力。

还好,玩过的galgame里还有跑在NScripter上的(例如月姬、水色)和跑在吉里吉里2/KAG3上的(例如Fate/stay night)。
前者有公开的SDK而且教程众多,然后也有非官方的开源实现ONScripter;
后者不但有SDK而且游戏引擎本体还是完全开源的,简直再适合学习不过了。
——后来回过头看吉里吉里2的时候才觉得,呃真的很适合学习么…好吧或许当时的我别无选择。

这两者放在一起,当时的我瞬间觉得吉里吉里2各种高大上,而NScripter虽然加各种插件很方便但自身好弱。
(后来才领悟到NScripter自身不加插件也可以非常强大,只要有耐心…[注1])

就拿这俩引擎的入门教程看,
NScripter的脚本长这样(我现编了一段):
*define
; 上面是脚本开头的标签。这行是一行注释,下面是声明要用全局变量的命令

globalon

; 给变量槽赋予有意义的名字
; 先来几个普通变量
numalias 50, tmp
numalias 51, arg0

; 再来个全局变量
numalias 500, glob

; 随便定义个过程
*set_glob_if_greater
  if %arg0 > %glob mov %glob, %arg0
  return

*test_sub
  mov %arg0, %tmp
  gosub *set_glob_if_greater
  return

game

*start

mov %tmp, 0
mov %glob, 0

; 显示一行文字
一开始tmp变量的值是%tmp\
看清楚了么?
br

mov %tmp, 42

现在tmp变量的值变成了%tmp\
也没啥神奇

gosub *test_sub

顺带给全局变量glob也设上了值%glob,收工咯
br

end

吉里吉里2的底层脚本长这样(引用自我的老帖 TJS2中对象的表示方法,其代表的运行时环境,与闭包的关系):
class A {

    var value = "A";
    
    function printField() {
        System.inform(value);
    }
    
    function printFunc() {
        print2();
    }
    
    function print2() {
        System.inform("A.print2");
    }
}

class B {

    var value = "B";
    
    function print2() {
        System.inform("B.print2");
    }
}

var a = new A();
var b = new B();
(a.printField incontextof b)(); // 使用incontextof运算符显式改变上下文
(a.printFunc incontextof b)();  // 这两行调用的上下文被替换为与b的相绑定
a.printField();  // 正常的方法调用
a.printFunc();   // 这两行调用的上下文都与a的绑定
// 运行结果: 依次显示B, B.print2, A, A.print2
其上层的KAG3脚本长这样(懒得编了,下面引用自Studio ついんくる):
*choice|決断
[er]
人の流れを読むんだ…。[l][r]
俺がはじめに訪れるべき方向はどっちなのか…。[l][r]
[link target="*east"]東だ![endlink][r]
[link target="*west"]いや、西へ行こう。[endlink][r]
[s]

*east|東
[er]
東に来た。やはり混んでいる。[l][r]
[eval exp="f.flag = 1"]
[jump target=*which]
*west|西
[er]
西に来た。やはり混雑は無い。[l][r]
[eval exp="f.flag = 0"]
[jump target=*which]
*which
俺は…
[if exp="f.flag == 1"]
お世辞にも勝ち組とはいえないようだ。
[endif]
[if exp="f.flag == 0"]
いい判断をしたようだ。
[endif]

KAG3有着NScripter脚本针对视觉小说类游戏的便利性,而底下的吉里吉里2的脚本则有强大的灵活性,吉里吉里2与KAG3两者结合起来的整体简直完美。

我试着用NScripter和吉里吉里2/KAG3写了些学习用的小游戏自娱自乐,还买了几本书专门学习如何用这俩引擎制作游戏:

然而只是使用现成的游戏引擎还是让我心里痒痒的。我就进一步钻到吉里吉里2的源码里去学习去了。一开始雄心壮志想把整个引擎都学通了,包括里面的图像处理、排版引擎、视频处理、音频处理、脚本引擎、扩展机制,以及最上层协调各个组件的游戏引擎总控。
源码读了几轮,先是粗略的快速瞄一遍,然后逐步细化,然后…发现全部都彻底理解的话要同时学的知识实在是太多了,而最吸引我的、感觉最好玩的还是其中的脚本引擎部分。

吉里吉里2的底层脚本语言名为TJS2,是一门动态的、类Java/JavaScript语法的、基于类的面向对象编程语言,用C++实现。
当时的我手上能用的编程语言就C(入门级)、Java(还算熟练)、JavaScript(凑合用)、C++(非常入门级),这TJS2正好跟其中两个的语法都很相似,所以感觉很亲切。
而且我正好对Java语言到底是如何实现的深感兴趣,这TJS2跟Java长得那么像,自然是非常吸引我去深入了解它的实现。

当时我一点编译原理都不会。这吉里吉里2的TJS2部分看了很多遍就是无法形象的理解每一步里面到底再干什么。为此我开始自学编译原理,在书本与吉里吉里2的代码之间来回切换着看,同时也试着自己写写小的编译器和解释器,过了好几周终于开始觉得能看懂那么点名堂了——TJS2的实现是:
  • 手写的词法分析器
  • 用Bison生成的LALR(1)语法分析器,内嵌语义动作
    • 边做语法分析边生成部分AST——每个语句生成一棵AST(包括里面的表达式)
    • 边做语法分析边生成字节码——每个语句parse完就从AST生成出对应的字节码,并释放AST的空间
  • 基于寄存器的字节码作为中间代码
  • 直观的switch分派式解释器
  • 基于引用计数的自动内存管理(是的,循环引用会导致内存泄漏)
  • 基于discriminated union的值表示方式
  • 类似COM的IDispatch的接口设计
  • 基于哈希表的对象实现
  • 类似JavaScript的核心库设计,包括Object、Function、String、Math、Date、RegExp之类

不得不说,吉里吉里2作为一个简易游戏引擎,其组成部分还是相当完整的。这TJS2虽然实现得很啰嗦,有些基础设计也不太好,但总归很完整,够当时的我学习好久了膜拜好久了。

在初步对吉里吉里2有点理解之后,每当碰到用它实现的游戏我都毫不犹豫的先把它的脚本都拆出来,好好学习一番之后再玩——或者游戏实在好玩的话为免被剧透有时候也先玩了再学习(ry
所以玩基于吉里吉里2的日文游戏我从来都不需要挂AppLocale或者NTLEA之类,只要把脚本拆出来转换成Unicode(UTF-16LE)之后再玩就不会乱码了。
然后有些游戏有时候会报bug,让吉里吉里2引擎进入调试模式;此时我还真的就调试一把,试着把脚本里的bug找出来,修了继续玩,不用等官方发补丁。

从吉里吉里2出发,后来我又看了许许多多的语言实现,包括但不限于 吉里吉里3、CPython、CRuby、Lua、老SpiderMonkey、老JavaScriptCore、老KJS、IronPython、JRuby、Rubinius、SSCLI等等。再到后来终于开始回归Java这个起点,开始看OpenJDK里的HotSpot VM的实现。
过程中我逐步学习到了如何实现一门编程语言的方方面面,特别着重于如何让编程语言跑得更快——这样就走上了参与实现JIT编译的路。

一晃眼从当初学习吉里吉里2开始已快10年,而我也已经从事JVM研发工作也有5年多了。

(哎哟糟糕,已经写得太长了…下面写短点)

=====================================================

从galgame到逆向工程

跟上文并行的还有另外一条线索。

在略微入门吉里吉里2的实现后,我对其它不开源的游戏(引擎)的好奇心就更加按耐不住了。但它们没源码没文档,怎么深入进去学习?
——逆向工程。

我正好在大二的暑期课程里选修了侯捷老师上的课,以MFC框架为例子来介绍应用程序框架的设计。
其中引起我注意的是他介绍CArchive这个类的设计与实现。这是个非常好的例子,让我学习到了数据归档文件的一般架构,对其中的索引部分、数据部分大概是什么样子的有了个数。

碰巧,就在我刚上完这选修课之后,有朋友拿着一个游戏的数据文件来问我能不能帮忙看看。他们说那个文件看似全部都是明文的剧情脚本,但开头和中间有些莫名其妙的“乱码”。他们想翻译那游戏,但困于中间的乱码,弄起来总是别扭。
我拿到数据文件一看,啊哈!这不就是个典型的归档文件么:开头是索引元数据,后面是带标记的明文数据。然后马上就写了个拆包/封包的工具给朋友用。得来全不费功夫,我连那游戏引擎自身是如何读取这个文件的都没看,但却是从这里开始沾上了逆向工程的边。

后来在朋友之间传开了消息,说我可以帮忙拆galgame的数据归档啥的。但实际上当时我所掌握的技能,能应对的东西还很少。
于是不断遇到新的游戏,不断学习新的办法来处理。

  • 先是遇到了有明文索引,而数据内容可选压缩的归档文件,LZ系的压缩、解压算法都好好学习了一遍,特别是LZSS的某个变种;
  • 然后又碰到了有明文索引,而数据内容不但压缩了而且还简易异或加密过的;
  • 接着又碰到了(貌似)更彻底的加密,例如Blowfish;
  • 后来还遇到了连索引也给隐藏了起来的神奇的格式;

这就像打怪练级一样,每次遇到新的无法解决的问题,都可以在尝试解决的过程中学到新的知识。那兴奋感是无与伦比的。
而且过程中我不断的需要做汇编级调试,积累下了不少经验,也锻炼了解决问题时的耐心。
这不,我现在的研发工作就经常需要做汇编级调试,要是没以前玩游戏做逆向工程的经历,真是难得耐下性子来调试枯燥的程序。

那段经历让我学习到了归档文件的各种可能,各种通用无损压缩算法,各种对称加密算法,一些图片文件的格式(以及某些常见的自定义图片格式的思路),一些音频/视频文件的识别方式,还有最让我感兴趣的:
其它galgame引擎的核心执行引擎是如何实现的!说来这也是千奇百怪,
  • 有一行一行解释执行明文命令的;
  • 有把高级的脚本文件明文放在归档里的,例如吉里吉里2;
  • 有把脚本变成token流存在归档里的,例如FFD System;
  • 有把脚本编译成字节码解释执行的;
  • 还有的自带JIT编译器!例如AliceSoft的System 4.x系。
  • 还有用C++之类的native语言写脚本的…orz

回想起来,那时一点点做汇编级调试,找出解释器的主循环,一点点的把字节码的格式和语义探明,那也是乐趣无穷啊。
回过头再拿拆解出来的字节码设计跟当时我心中的“标准模型”的吉里吉里2来对比,又能感受到那些游戏引擎们各自不同的设计取舍,渐渐的就把galgame引擎的执行引擎的常见套路都摸清楚了。

有源代码的,读代码;[注2]
没源代码的,拆了来读。

摸清套路后,再去拆解新的引擎就顿然变得无趣了,感觉只是机械的在寻找和确认一些已知的套路,已经不怎么能学到新东西了。
然后我就终于不再对拆解galgame有任何兴趣,转向专注于学习实现优化的JIT编译器去了。

放俩传送门重温下当时的感受:
[原创]以ef - the first tale. / Trial Version中资源文件的加密的解析简单介绍几个工具
Help, minori's .paz format.. - gemot encubed

掌握了拆解数据归档文件和分析脚本格式的技能之后,玩galgame玩得想找攻略的时候,总是可以试试把脚本拆出来确认下游戏逻辑到底是怎样的。这不是作弊嗯不是作弊(逃
例如说当看到某游戏的某音乐会门口的选项在脚本里确实就是个假选项的时候,我只能服了写脚本的人的险恶用心…ToT

有一次有个朋友想用吉里吉里2做个中文的galgame在漫展上卖,不知道跟 @Mili 大大是什么关系反正米粒大大就有帮他们鼓捣游戏引擎的周边工作,例如说封包上的数据保护。然后我跟米粒大大玩了一次有趣的攻防战,对方的目标是尽可能不让我能轻松的dump出剧情脚本和图片资源,而我的目标是尽可能找出批量dump出数据的办法,来告诉对方破解的难度。
在我的印象中我都成功的把数据dump了出来…米粒大大还记得那时候的事不?

=====================================================

从galgame学习了日文

直接放传送门好了 真的有为了玩日本成人游戏而去学日语这种人吗? - RednaxelaFX 的回答

=====================================================

显然,上面两条线索里我做的事情都是极其消耗时间和精力的。
大部分事情都发生在我大三的时候。这意味着…

这是个非常黑暗的故事。

我大三整个学年的成绩都毁了。毁得彻彻底底,一个学分也没进账,因为期末考试我也没去参加。
大三结束,毫无疑问是我人生至今最低谷的时候。一方面学习到了许多课外知识,而另一方面人生已经走到了悬崖边,就快撑不住了。
我跟身边的人的人际关系也陷入了危机,对生活中的许多事都不闻不问,能躲就躲。

为了了解自己喜欢的游戏,为了能创作游戏或者游戏引擎,付出这样的代价,是否值得呢?至今我也无法回答自己的这个问题。

至少,让现在的我能有一点安慰的是,当时低谷中积累到的经验,现在还算是能在工作中用上,并不是完全虚度了光阴。

=====================================================

* 注1:看这个用NScripter脚本实现了一个LISP解释器“NScLisper”的游戏:魔法言語 リリカル☆Lisp,源码在此:zick/Magical-Language-Lyrical-Lisp · GitHub。简直黑魔法,它居然还实现了mark-sweep GC,真是好好展示了NScripter脚本的能力。NScLisper的主体在0.txt。

* 注2:像吉里吉里2系的、RenPy系的当然好办;但也有些有趣的原本不开源的游戏突然变成开源的了,例如Leaf/Aquaplus的TH2X、TtT、Aruru、Kusari啥的真是意外的收获 >_<