如果要讲 GM8 缺少的功能,那我可以从天亮一直讲到天黑。所以,这里我姑且不去谈那些 GM8 没有的功能,光说说那些实现了的功能中,令人窒息的智障设计。

ICC 早于 Create

描述:ICC 即 Instance Creation Code 的执行顺序早于实例的 Create 事件。

ICC 早于 Create 是个极度弱智的设计,这极大增加了 GM8 泛化对象的难度。想象一下,我们在设计一个泛用对象,我们通过在 ICC 中控制变量(假设是 num,spd,dir 三个变量)的值,让泛用对象展现不同的状态。但是假设有一种情况是最普遍的,我们会很希望给这三个变量赋予默认值,理想状态下,如果在 ICC 中没有赋值,那么变量就使用默认值。这里问题就浮现出来了,GM8 并没有给你任何地方可以设置变量的默认值,如果你在 Create 事件中给变量赋予默认值,由于 ICC 早于 Create 执行,ICC 等于没有,不管你 ICC 怎么赋值,Create 事件都会将其覆盖为默认值。然而,除了 Create 以外,我们并找不到其他事件可以承担赋予默认值的功能了。那么,这里就出现了两个解决方案:

  1. 使用 variable_系列函数检测变量是否存在,如果不存在,则赋予默认值。
  2. 在 Global Game Settings 里打开“将未初始化的变量值设为 0” 选项,判断变量为 0 赋予默认值。后者的问题是非常明显的,一旦取值范围包含 0 但是默认值又不是 0,就会引发很多问题,所以我们通常建议使用前者。

不管怎么说,为变量赋予默认值这种基本操作要写额外的判断着实令人恼火。所幸的是,YYG 也意识到这是一个极度智障的设计,所以在 GMS 之后,ICC 被挪到了 Create 之后,这样就可以放心大胆地在 Create 里直接初始化变量了。

数组不是类型

描述:GM8 只有 real 类型和 string 类型,数组不算类型。

在 GM8 中,数组完全不被当做一种类型看待,相比之下,GM8 似乎更倾向于把形如 arr[1] 的形式当做一个单独的变量来看待。数组名在 GM8 中与 0 号元素等效,即 arrarr[0] 是同一个东西,这就意味着你无法直接传递数组,你不能把数组传递给脚本中,也不能从脚本中返回一个数组,更不能直接把一个数组拷贝到另一个数组。这间接引发的问题是,GM8 没有提供也无法提供任何用于操作数组的函数,因为你根本无法将数组传递给函数。另外,你必须自己使用变量来记录数组的大小(不要说什么 C 语言也是这样,C 语言至少可以直接传递数组名)。这种丧尽天良的设计在写脚本的时候着实令人头疼,如果你一定想要传递数组的话,唯一的解决方式大概就是用 global.arr[] 传递数组吧。

GMS 中数组的地位有所提升,直接 show_message(数组名) 的话,得到的是串十六进制的地址值。同时,它也提供了数组的访问器(即 arr[@ ind])让脚本可以直接修改参数中的数组的值,而不需要通过返回值再传递回去。因此,GMS 也得以提供一些可以操作数组的函数。

数组限制二维

这个没得讲,YYGSB 就对了。写算法的时候只能开二维数组脑袋都会气炸掉。而且 GM8 数组名不像 GMS 那样是一个地址,不能用数组套数组的方式曲线救国。我姑且也是写过解释器的,底层在实现二维数组和三维数组时几乎没有增加任何代码上的难度,单纯是多一次迭代而已。对于 YYG 一直拖到 GMS2Q4 才开始考虑支持多维数组我个人是完全不知道他们在想些什么。

字符串中字符索引起始为 1

GM8 所提供的任何一个操作字符串的函数,例如 string_char_at(), string_pos() 等,其参数或返回值中的索引,均把第一个字符的索引视为 1。

在编程界几乎完全统一以 0 作为第一个索引的大环境下,GM8 中大部分索引也是从 0 开始的。但是唯独字符串,YYG 是铁了心要它从 1 开始,即使到了 GMS2 中,字符串索引依然以 1 开始,这种与业界对抗的自信令我不得不为 YYG 鼓掌。当然,是嘲讽意味的。

var 的作用范围

众所周知,在 GM8 中

1
2
3
4
5
6
if (true)
{
    var a;
    a = 1;
}
show_message(a);

会显示 1。

对于任何一个学习过其他编程语言的人而言,这都是反直觉的,因为不管什么语言里,临时变量的作用域都不应该超出花括号,在花括号外访问 a 理应报错。而 GM8 中,声明临时变量的作用范围是整个代码段。这种超出直觉的变量作用范围,很容易带来诸多 BUG。

限制 ini 路径与 exe 同目录

这个我就真不懂了。ini 就一破文本文件,我找遍了天也没找到它要强制放在 exe 同目录的理由,GM8 的底层 Delphi 所提供的 Tinifile 组件也完全支持 ini 放在任何目录下,根本没有任何技术上的难点。

单等号判等

GM8 使用 = 同时充当赋值和判等的功能,这绝对是对任何一门编程语言的侮辱。拜其所赐,if ((a = func()) == noone) 这类在其他编程语言中十分常用的写法(给 a 赋予 func() 的返回值再与 noone 比较),在 GM8 中却变成了先判断 a 是否等于 func() 的返回值,再判断 a == func() 的返回值是否等于 noone。更鬼的是,a = b = 1 这种在其他语言中表示给 a 和 b 都赋值为 1 的语句,在 GM8 中变成了判断 b 是否等于 1,并返回判断的布尔值赋给 a。

神秘的花括号

BUG

如上图所示,你在 GM8 的代码编辑框中,如果以上花括号作为起始,那么你超过下花括号的代码会被告以“程序在这行代码前已经结束”的错误。但是只要你在前面随便写点什么,就不会报错:

BUG

这是一个很神秘的设计。我至今没有找到 GM8 要这么设计的理由。

Debug 开关写入 exe中

(2020.1.22 更新)

从果群大佬 Cube 得到的消息,GM8 将 Debug 开关写入了 exe 中,任何一个使 用GM8 编译的未加密的 exe,都可以将其 0x1E8488 偏移量处的 00 改为 10 来启动 Debug 模式——不仅是 Debug 窗口,代码中 if (debug_mode) 的模块也会被启用,这无疑为玩家作弊提供了极好的条件。

(2020.4.8 再更新)

0x1E8488 偏移量并非绝对的,由于图标保存在该偏移量之前,如果图标太大,文件头会扩大,该偏移量会后移。准确的办法是搜索 1234321(即小端模式下的 91 D5 12 00),搜索结果中 91 D5 12 00 20 03 00 00 00 00 00 00 20 03 00 00 这一行的第九个十六进制数改为 10

——未完待续?