缩进

  • 每一层花括号内,都要比上一层花括号缩进四个空格(或一个制表符)。
  • ifwhiledoforrepeatswitch 的主体要缩进,不管有没有花括号。
  • 同一个层代码要对齐,否则就失去了缩进的意义。

例:

Example

换行

  • 一行只写一条语句,写完一条就换行。
  • 过长的代码在中间断开换行,并且换行后要缩进一次。注意函数调用只在参数的逗号后进行换行。
  • 不同的代码块之间使用空行隔开。

例如:

Example

分号

任何语句的结束处都使用一个分号来表示。

空格

  • 所有双目运算符两边都要有且只有一个空格。包含 +(加号),-(减号),*/===!=><>=<=+=-=*=/=&&||^^&|^<<>>&=|=^=
  • 函数参数间的逗号只在后面留空格。
  • 单目运算符不留空格,包含 ~!+(正号),-(负号)。
  • 成员运算符(即 .)两边不加空格。
  • 括号,数组运算符(即 [])外侧不留空格,内侧可留可不留。
  • ifwhileswitchfordo 等关键字与括号之间可留空格,也可不留。

减少 if 的嵌套

我们来看下面两段代码:

①:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
if (instance_exists(ObjRayLoli))
{
    inst = instance_place(x, y, ObjRayLoli);
    if (inst != noone)
    {
        findRayLoli = true;
        if (inst.isLoli == false)
        {
            inst.isLoli = true;
            if (inst.num < 10)
            {
                goodRay = true;
                badRay = false;
                inst.num += 1;
            else
            {
                goodRay = false;
                badRay = true;
                with (inst)instance_destroy();
            }
        }
        else
        {
            goodRay = true;
            badRay = false;
        }
    }
    else
        findRayLoli = false;
    }
}

②:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
if (!instance_exists(ObjRayLoli))
    exit;

findRayLoli = true;
inst = instance_place(x, y, ObjRayLoli);
if (inst == noone)
{
    findRayLoli = false;
    exit;
}

if (inst.isLoli == true)
{
    goodRay = true;
    badRay = false;
    exit;
}
inst.isLoli = true;

if (inst.number >= 10)
{
    goodRay = false;
    badRay = true;
    with (inst)instance_destroy();
    exit;
}
goodRay = true;
badRay = false;
inst.num += 1;

这两段代码是完全一样的东西。但是代码 ② 将 if 全部拆开,将“期待的状态下”的代码一条线写下来不缩进,而“非期待的状态下”的代码则通过 if 处理,并且 exit 出去(脚本中则使用 return)。这样做的好处是,在看代码时只要忽视掉所有的 if 分支,就能快速掌握这段代码期待什么样状态,做什么样的处理。而代码 ① 层层叠叠,让人搞不清主旨。

统一命名与区分命名

同一类型的名称应该统一命名,不同类型的名称应当有不同的命名方式。

在 GM8 中,通常我们为资源名、脚本名、普通变量名、全局变量名、局部变量名区分命名,换而言之,你需要能够看到一个名字知道他是什么类型的名字。

不管你使用哪一种命名方式,在整个游戏工程中要做到统一,同一种类型的名字不要使用两种或更多不同的命名方式。

另外,不要在代码中出现默认资源名(即 sprite12object334 等),这会极大降低代码的可读性,请尽可能为所有的资源命名。

资源名的常用命名方式

驼峰命名法,以资源类型缩写 + 模块 + 功能的格式命名。如 sprStage3BlocksndFinalBossAttackobjPlayer 等。

混合蛇形和驼峰命名法,以资源类型缩写 + 下划线 + 模块 + 功能的方式,例如 spr_stage3Blocksnd_finalBossAttackobj_player,这样的好处是将资源类型单独隔开,更加一目了然。

不建议为资源名使用蛇形命名法,即形如 spr_stage3_blocksnd_final_boss_attack,因为蛇形会让名字拉的很长,在 GM8 的资源树中看着会很费力。

脚本名的常用命名方式

大部分编程语言使用驼峰命名法来命名函数(即 GM8 的脚本),我们也可以使用驼峰命名法,通常以动词 + 名词的形式定义脚本名,如 saveGamesetGlobalOptionsmakeCircleBarrage 等。

另外,我们也可以按照 GM8 的内置函数那样,使用蛇形命名法,通常和驼峰命名法相反,使用名词 + 动词或者名词 + 动词 + 名词的方式命名,如 game_saveglobal_set_optionsbarrage_make_circle 等。

普通变量的常用命名方式

通常,普通变量的命名方式和脚本名的命名方式保持统一即可,这也是大部分编程语言通用的代码风格。由于 GM8 会对脚本名染色,因此无需担心无法区分。变量名的命名要求是能够轻松看出其作用,不要使用一些不明其意的简写,如 deplc1c2 等。

通常,有一些约定俗成的变量名,例如:

  • ijk 表示循环变量
  • UDLR 表示上下左右四个方向或方向键
  • str 表示字符串
  • dir 表示运动方向
  • spd 表示速度
  • rot 或者 ang 表示旋转角度
  • inst 表示实例
  • v- 前缀表示纵向,h- 前缀表示横向

等等。对于约定俗成的变量名而言,尽量不要让他去表示其他的意思,以免造成误会。

全局变量(globalvar)的常用命名方式

使用全大写蛇形命名方式。如 MAX_ENEMY_NUMBERSAVE_FILE_PATH 等。

使用特殊前缀。如 gv_maxEnemyNumbergv_saveFilePath 等。前缀是自定义的,只要你觉得能够一眼看出来即可。

或者,干脆不要使用 globalvar,坚持所有全局变量都使用 global.xx 的形式,这样命名方式和普通变量保持一致即可。

局部变量(var)的常用命名方式

由于局部变量不像全局变量一样一旦声明就一直存在,局部变量只会在限定的范围内使用,通常,即使不与通常变量区分,也很少会互相影响。另外,由于局部变量的作用范围狭窄,即使只使用简单的字符命名也很好看出其作用,因此局部变量常常使用单个单词命名。如果要将局部变量和普通变量加以区分,可以考虑以下命名方式:

使用下划线前缀。如 _file_width_map 等。

和全局变量一样使用特殊前缀,如 lv_filelv_widthlv_map。前缀是自定义的,只要你觉得能够一眼看出来即可。

注释

多写些注释总是好的。尤其是对于某些记录状态变量,要使用注释说明每一个值分别是什么状态。

例如:

Example

高内聚,低耦合

所谓高内聚,就是指一个模块专心实现一个方面的功能,不要插手其他方面的功能,让其他模块去实现,然后再调用其接口。

所谓低耦合,就是指不同模块之间仅使用一些规定的接口来互相访问,而不要直接插手其内部,减少对象与对象之间的联系。

通常来讲,一个模块内有多个对象,会创建一个专门用以管理的对象(通称 Manager)。Manager 不仅协调模块内的正常运转,并且其他模块访问这个模块的功能时,只通过 Manager 访问调用,而不会直接去访问内部的各个对象。同样的,模块内的对象想要使用另一个模块的功能,也要通过那个模块的 Manager。

接口(Interface)在其他语言里通常指公有成员函数,但是由于 GM8 并没有提供成员函数的语法,因此我们在 GM8 更多地使用变量来充当接口的功能,这些变量应该在 create 事件中初始化为默认值并且说明每一个变量修改了会产生什么影响。

高内聚低耦合的思想十分方便在 debug 时定位 bug 所在位置。

泛化

对于一些比较近似的功能,应该对其提供泛化。泛化即通用化,将一些数值更换成变量,让它能够通过修改变量的值达到不同的效果。

例如,我在 A 处需要生成 10 个弹幕组成的速度为 5 的扩散圆,在 B 处需要生成 25 个弹幕组成的速度为 10 的扩散圆。那么我们就应该把生成扩散圆的功能泛化为脚本,提供 x, y, object, number, speed 等参数,然后使用这些参数来创建扩散圆,而不是在 A 处和 B 处各自写一套自己的代码。

同样,我们在实现任何功能的时候首先要想一想未来有没有再次利用的可能,是否需要将其泛化。