gamemaker吧 关注:13,643贴子:95,088
  • 7回复贴,共1

关于GMS2 YYC编译的研究

只看楼主收藏回复

GMS2在输出游戏时提供两种编译模式,一种是VM,也即默认编译模式,一种是YYC。
VM就是字节码,这样的输出虽然快,但是有被破解的风险。
YYC则是将GML转化为cpp进行输出,尽管编译时间相比VM来讲长的多,但是造出来的程序是几乎不可能被破解的,此外,YYC游戏的处理效率远高于VM编译。
因此,有时候使用YYC编译就显得很必要了。
这里给不知道的同学们提一下,切换地点就在GMS2的右上角,那个像是个瞄准器的图标,点下去,在Output窗口中切换为YYC即可。
但是,YYC有很多毛病——这也是我今天要讲的问题。
有时候,你在VM中编译起来毫无问题的游戏,转入YYC后,你会发现bug奇多——而且这些bug不是你能想象的。例如,instance_create_depth与instance_create_layer,往常,哪怕你的代码再混乱,最起码创建的实例也是正确的。
但是,在YYC中,可能它创建的实例根本就不是你想要的。你填的是obj_123,它有可能给你创建obj_456。实际上,这还不是最糟的,它最后的运行结果可能会比这还要可怕的多......包括但不限于出现死循环,出现你根本没调用的函数——从某种意义上,它的创造力比Shader的创造力还强。
这就是我们今天探讨的问题:到底该怎么减少这种错误编译?
仅据个人研究,如果你的码风(代码风格)符合“某种规范”,可以大大减少这种错误率。
首先,你必须尽一切可能地减少那些不符合大众所认可的码风,包括但不限于:语句中不能省略括号,逻辑判断必须使用双等于,每一行结束后添加分号以示结束......
但是这并不是我们所需要的全部。即使你完全遵循这些公认码风,YYC的编译依然会出错。
以下是某个示例:

这是可以在YYC中运行的代码(注意图中仍然有些问题,如等于号前后的空格问题)。
这些代码有个很奇怪的现象,第六行,第七行的临时变量似乎是完全不必要的,你完全可以在下文中的instance_create_depth中将它们替换掉,不是吗?
按照常规理解确实如此,但是,事实证明,如果我不这么干,在执行这里时,创建的实例是一个叫做“obj_system_heart”的实例。
事实上,各种平日里可以在函数里直接写入的表达式,似乎都必须使用一个变量储存表达式的值,然后在函数内写入变量......反正很莫名其妙。
现在,我想请教的问题是,还有什么码风是能降低YYC编译错误概率的?又或者不是码风,总之,什么能规避YYC的出错?
(是的,这其实不是个科普贴,是个请教问题的贴子)


IP属地:美国1楼2020-02-29 20:24回复
    现在有了点新的研究。
    事实证明,YYC要求极其严苛的语法,几乎不能有一丝问题,平日里省略的括号必须被加上。
    一个很好的示例,是这样的:
    a = b == c || d;
    这个式子看着乱七八糟,现在,请先试着在心里把它用括号标出来求值顺序。
    ......
    好了。通常情况下,我们想要的顺序,是这样的:
    a = ((b==c)||d);
    这也是Windows上的求值顺序。但是,对于YYC来讲,你的括号必须被加上,如果不加上,YYC就有可能错误地理解你的求值顺序,比如会变成这样:
    a = (b==(c||d));
    这样一来,程序可能就会从此走向一系列你不想要的产物......最后彻底坏掉。


    IP属地:美国2楼2020-02-29 23:58
    回复
      还有一项新的发现是关于一楼说的“某种码风”的具体定义。
      就像是GMS2的帮助手册中所写,你需要显示地调用。红激汉化的手册中原文如下,我在此搬来:
      ---------------------------------------------------
      当使用GML编写游戏时,您应该知道函数调用参数不能保证评估顺序。这意味着,您在代码中放置函数的顺序将从平台变为平台,因此您应该以显式的方式对它们进行编码。这是由于不同的目标平台之间的优化差异,例如在Windows的目标函数上可以从右到左计算,但是在HTML5的目标上,它们可以从左到右进行评估。为了避免任何问题,你最好不要在函数调用的参数中调用多个函数,因为你很可能依赖于评估的顺序。
      要查看这意味着什么,请考虑下面的代码,它调用了几个函数,并将它们用作脚本的参数:
      buffer_seek(buff, buffer_seek_start, 0);
      scr_buffer_get_info(buffer_read(buff, buffer_s8), buffer_read(buff, buffer_s16),buffer_read(buff, buffer_s16));
      现在,这里的问题是,在某些平台上,最后的buffer_read将被调用,因此脚本的所有参数都将是错误的,因为数据正在从缓冲区中以“反向”顺序读取,正如您所看到的那样。这将会影响到buffer_read函数的所有其他值,因此传递给这个脚本的所有参数都是错误的!
      要解决这个问题,您应该显式地调用所需订单中的函数,并将返回的值存储在变量中,如下所示:
      var val[0] = buffer_read(buff, buffer_s8);
      var val[1] = buffer_read(buff, buffer_s16);
      var val[2] = buffer_read(buff, buffer_s16);
      scr_buffer_get_info(val[0], val[1], val[2]);
      虽然它可能看起来比较冗长,但它可以保持一切清晰,并避免任何可能出现的问题。
      ---------------------------------------------------
      是的,YYC同样要求这样的语法。如果你不这么做,就可能会让函数瞎几把调用参数。
      现在我的怀疑是,我一楼神秘创建的obj_system_heart就是argument[0]+obj_character.x_extra对应的值所对应的id。因为,我的obj_system_heart所对应的数字还真的就和那个值差不多。


      IP属地:美国3楼2020-03-01 00:01
      回复
        说几点我的经验:
        1. 操作符+-*/&&||等,在F1手册里明确写了要求加好括号,否则即使是vm编译,在不同平台也可能导致不同求值顺序
        2. gml对函数参数进行求值的时候,似乎确实是从后往前,特别是buffer这很明显,之前我在gms1即使vm编译也是如此
        3. yyc确实更严格。前几年在gms1用yyc的时候,执行报错,后来检查发现确实是我代码有问题,甚至不知道为什么vm能够正常运行,具体什么情况忘了。。而且记得当时yyc如果编译出错,就清理一下工程,然后再重新编译,就可能会恢复正常。
        4. gms2之后没再用过yyc,有什么变动就不清楚了


        IP属地:上海来自Android客户端4楼2020-03-01 18:43
        回复
          0v0要我装个啥VS 我没装


          5楼2020-03-02 01:20
          回复
            还没用过YYC,理解一下就是,运算符要加上括号,函数不要使用嵌套,这样??
            加括号咱倒是习惯了,不要嵌套这个……emmmmmmmm


            IP属地:北京6楼2020-03-02 09:48
            回复
              我觉得这该是厂商需要优化的问题,可以多发发邮件,我原来有问题发邮件,他们都回了,不过效率很低,到现在还没改,不过他们还是有列计划表的


              IP属地:浙江7楼2020-03-02 14:27
              回复
                函数调用的时候参数顺序都可能乱,这明显不是什么特性而是实实在在的bug


                IP属地:北京来自iPhone客户端8楼2020-03-23 20:38
                回复