编程时的最佳实践

在本页中,我们将介绍游戏编程时的一些"最佳实践",同时解释一下GameMaker的内部工作原理。然而,在继续之前,值得注意的是两个非常重要的要点:

说到这里,让我们继续前进,看看一些编写好的GML代码的一般技巧,你可以随时应用。

编程风格编程风格

在编写代码时,每个人都有一个风格。你编程时使用的风格是你放置括号的方式,你如何标记行,以及你如何声明和命名变量等,并且对于使你的代码清晰易读是必不可少的(当你不得不在其他事情上花费一段时间后回到这个项目时,对你未来的自己来说)。

有很多很多编程风格,有些人会争辩说他们的风格是最好的,但事实是,只要你在使用它时保持一致,并且一切都是清晰明显的,几乎任何风格都是好的。

Code Style Example上图是一个脚本中函数声明的例子,用来说明以上几点。你可以看到它使用了JSDoc风格的注释来清楚地解释它的全部功能,并且编码风格是一致的,有4个空格缩进,下划线用于本地变量,日志输出等...

还要注意的是,虽然脚本编辑器允许你在每个开/闭括号处折叠代码,但你可以使用#region#endregion标记来分割代码的一部分,并大大增强可读性,特别是在处理包含多个函数的大型脚本时。区域也可以注释-请参阅手册中本页的编辑部分):

Example Of Using Regions在编写代码时,您应该注意,在编译最终游戏时,GameMaker会删除注释,删除不必要的换行符和空格,替换常量/宏/枚举值,这意味着你可以根据需要在代码周围添加尽可能多的空格,而不必担心保持注释简短或只使用它们有节制地

 

使用局部变量使用局部变量

从上面关于编程风格的观点继续,很多初学者做的一件事就是尽可能多地塞进一行代码中。例如:

draw_sprite(sprite_index, image_index, x + lengthdir_x(100, point_direction(x, y, mouse_x, mouse_y)), y + lengthdir_y(100, point_direction(x, y, mouse_x, mouse_y)));

虽然不是完全不可读,但它是低效的(例如,point_direction()函数被调用了两次),而且看起来很混乱和笨拙。它会更好地表达为:

var p_dir = point_direction(x, y, mouse_x, mouse_y);
var local_x = x + lengthdir_x(100, p_dir);
var local_y = y + lengthdir_y(100, p_dir);
draw_sprite(sprite_index, image_index, local_x, local_y);

创建这些局部变量所需的内存和资源可以忽略不计,而且你(或任何其他阅读代码的人)从它的清晰度中获得的即时好处远远超过了它。同样的想法也应该应用于函数,你应该为输入变量分配合理的名称,并在需要的地方使用清晰的格式和局部变量,以使其尽可能可读。

局部变量在游戏中处理速度很快,所以要充分利用它们,如果一个表达式在代码块或脚本中出现两次或更多次,考虑为它创建一个局部变量。当使用YoYo编译器(YYC)目标时,如果引用global如果函数或代码块中多次出现局部变量或实例变量,则在代码开始时将它们赋给局部变量,然后引用该局部变量,因为这将提供给予更好的性能。

 

数组数组

数组使用速度快,需要的内存比数据结构少,但它们还可以进一步优化。当你创建一个数组时,内存是根据它的大小分配给它的,所以你应该先尝试将数组初始化到它的最大大小,即使你不打算以后再填充它。例如,如果你知道你需要一个数组最多容纳100个值,你可以直接使用array_create()函数将其初始化为100个插槽:

array = array_create(100, 0);

这将在一个"chunk"中为它分配内存,所有数组值都设置为默认值0,并有助于保持快速,否则每次向数组添加新值时,整个内存都必须重新分配。

注意在HTML5目标上,这样的数组赋值不适用,你的数组应该从0开始初始化!你可以通过检查os_browser变量来轻松处理这个问题,例如:

if (os_browser == browser_not_a_browser)
{
    array_create(100, 0);
}
else
{
    for (var i = 0; i < 100; ++i;)
    {
        array[i] = 0;
    }
}

您还可以通过将变量used设置为等于0来释放与数组相关的内存。因此,要从上面的代码示例中清除数组,您只需使用用途:

array = 0;

 

数据结构数据结构

GameMaker中,数据结构已经优化,比以前的GameMaker版本快得多。它们仍然需要清理当不用于释放内存时,它们仍然可能比数组慢,但易用性和处理它们包含的数据的额外功能通常会超过最小的速度差异,所以不要害怕在游戏中使用它们。

应该注意的是,在所有数据结构中,DS Maps特别快,无论是读取还是写入,都使其成为所有类型任务的绝佳选择。

前面我们提到了数组的访问器,但它们也可用于数据结构,这可以帮助清理代码并使其更容易阅读。

 

碰撞碰撞

GameMaker 中有多种处理冲突的方法,其中大多数都会带来一些额外的 CPU 开销。 collision_point_ 函数、place_ 函数和 instance_ 函数都依赖于边界框对房间内给定类型的所有实例进行检查,并且 虽然引擎内置了一些优化来限制这些检查,但碰撞从来都不是最有效的处理方式。 如果您开始使用精确碰撞,性能也会明显变差,因为您不仅要进行边界框检查,而且还要进行逐像素检查,这确实非常慢。

这并不是说您不应该使用这些功能,因为它们非常方便。 但是,您应该知道使用哪些以及何时使用,因为它们的工作方式都略有不同,并且速度也不同。 粗略的经验法则是,place_ 函数比 instance_ 函数更快,而 instance_ 函数又比 collision_point_ 函数更快,因此请阅读手册中的页面以了解相关信息 每种类型的功能,并确保选择最适合每种情况的功能。

或者,考虑创建一个基于瓷砖的碰撞系统,可以使用瓷砖贴图函数甚至是自定义2D数组或DS网格来创建。这些都将非常快,并有助于提高游戏的速度。但是,如果你使用的是不规则的地形或墙,以及不与网格对齐的对象,它们可能不合适。你可以找到一个关于平铺地图冲突的非常简单的教程从以下视频链接

 

纹理交换和顶点批纹理交换和顶点批

如果打开调试覆盖,在测试时,你会看到在屏幕顶部的括号中有两个数字。第一个是正在执行的纹理交换的数量,第二个是顶点批处理的数量。许多因素会影响这些数字,你永远不会让它们下降到(0)(0)因为发动机需要一个或两个每一步,但你的目标应该是让这些值尽可能低。

The Debug Overlay Bar对于纹理交换,最好、最有效的方法是优化精灵和背景在纹理页面上的存储方式。 这是通过精灵属性完成的,您可以在纹理组编辑器中创建纹理页面。 如果您有许多仅在主菜单中使用的图像(例如),请将它们放在单独的纹理页面上。 如果您有特定于关卡的图像,或者玩家和敌人等,情况也是如此。基本上,您希望按用途对它们进行分组,以便尽可能减少交换。 此外,为了帮助保持 VRAM 优化,您可以使用不同的预取和刷新函数根据需要从内存中加载和删除纹理。

注意如本页开头所述,如果您的游戏在FPS良好的情况下运行良好,则无需过多担心纹理交换.特别是在桌面目标上创建项目时。这些优化最适合用于大型游戏或低端移动的设备,如果使用不当,它们实际上会对性能产生负面影响。

顶点信息被"批量"发送到GPU进行绘制,通常批量越大越好。因此在绘制时应避免"破坏"批量,因为这会增加发送到GPU的顶点批量的数量。有很多事情会破坏批量,主要是混合模式,更改着色器,在绘制之前改变着色器的均匀性,以及使用内置的形状和图元进行绘制。

因此,如果你有许多使用bm_add混合模式绘制的子弹实例-例如-你将为每一个创建一个新的顶点批处理,这绝对是一件坏事!相反,在你的游戏中有一个控制器对象来绘制所有的子弹,像这样:

gpu_set_blendmode(bm_add);

with (obj_BULLET)
{
    draw_self();
}

gpu_set_blendmode(bm_normal);

注意这不仅仅适用于使用 bm_add - 任何混合更改都会破坏批次并影响性能。

这样,所有的子弹都会在同一批次中被抽取。 明智地使用函数 gpu_set_blendenable()gpu_set_alphatestref()gpu_set_alphatestenable() 可以极大地提高性能,并且可以启用/ 尽管它们可能并不适合所有类型的图形或项目,但可以根据需要在整个项目代码中禁用。

注意如果你不要求实例在使用控制器时以这种方式绘制自己,那么你可以简单地在绘制事件中添加一个注释来抑制默认的绘制,或者使实例不可见(尽管这将阻止所有绘制事件中为实例运行的所有代码)。

另一种减少这些数量的方法是禁用子画面的分离纹理页面选项,除非绝对必要。以这种方式存储的每个图像都被发送到自己的纹理页面,并以不同的方式进行批量处理,因此,将这些图像放在常规纹理页面上会更好。然后,您可以使用sprite_get_uvs()函数并将它们存储在变量中以备后用。这可能是一小部分额外的代码,但你将得到的提升是值得的。注意,这个方法不允许纹理重复!

正如所有这些提示,如果它使你的生活更难改变它,你的游戏运行良好,不要太担心它。

 

粒子粒子

粒子提供了一种非常快速有效的方法来在游戏中创建动态效果,通常它们可以提供给予良好的性能。但是,值得注意的是,在粒子上使用加法混合,alpha混合和颜色混合可能会降低性能,特别是在移动的目标上,所以如果您不需要它,请不要使用它!特别是加法混合,可以大大增加顶点批次,应谨慎使用。

请注意,在非WebGL HTML5目标上,具有多色,褪色的粒子将需要大量的图像缓存,并且将非常缓慢。然而,由于粒子精灵可以被动画化,因此您可以创建一个具有改变颜色的子图像的动画精灵,然后将其用于粒子。它仍然看起来像是逐渐的颜色变化,但不需要不断创建缓存图像。

您可以从粒子使用指南页了解有关粒子的更多信息。

 

表面表面

最后,我们将简要介绍如何使用表面,因为GameMaker在您的游戏中使用表面时有相当重要的优化功能:能够打开和关闭深度缓冲区

当你只是正常使用表面时,GameMaker实际上会创建一个表面和一个附带的深度缓冲区,以确保在3D中绘制任何东西时正确的深度排序。然而,对于大多数2D游戏,这个额外的深度缓冲区是不需要的,因此占用了额外的内存空间和处理时间,可以用于其他事情。这就是函数surface_depth_disable()开始发挥作用...

这个函数可以在你创建一个表面之前调用,以禁用深度缓冲区的生成,之后创建的所有表面将不会有为它们创建的深度缓冲区。你可以根据需要启用/禁用这个函数,甚至可以在游戏开始时调用一次,以禁用所有后续表面调用的深度缓冲区。它不会给给予很大的性能提升,但如果你的游戏严重依赖于表面,这肯定是要记住的,可能会阻止你的游戏在低规格设备上耗尽内存。

 

除了上面提到的主要内容,还有其他更一般的内容可以帮助提高性能,也被认为是"良好实践":

但是,正如我们在页面开始时提到的,所有这些优化都是可选的,如果你的游戏在60个顶点批次,80个纹理交换,添加剂混合等情况下运行良好,那么不要太担心!在编写下一个游戏时记住这些事情。