变量和变量的作用域


像其它编程语言一样 GML 使用 变量(variables) 作为大多数编程操作的基本单元。变量用于在设备存储器中存储信息以供之后(或即时)使用,并且它们被赋予一个名称,然后你可以在函数和脚本中引用它们。GML 中的一个变量可以存储许多不同的数据类型,如实数(如 100,2.456575,-56等...)、字符串(如 “Hello world!”)、整数(如 1,556,-7)或布尔值(truefalse)。

我们用变量来存储一个值以便在一个或多个操作或函数中使用。想想 “pi”,例如......它是一个真实世界的变量,其值为 3.14159265(等等......)。为什么?嗯,对某人说 “pi” 要比 “三点一四一五九二六五” 要容易得多!因此,命名事物会使生活变得更简单,这也意味着如果该变量的值发生变化,我们不必到处修改数值,因为变量 名称 仍然是一样的。在 GML 中,变量的名称必须以字母开头,并且只能包含字母、数字和下划线符号 “_”,最大长度为 64 个符号。所以,有效变量就像这样 fishfoo_barnum1,无效的变量会是 6fishfoo bar,或者*num

现在,在许多编程语言中,你需要先 “声明” 变量才能使用它。这基本上意味着你告诉计算机你要使用的名称,以便它可以在内存中分配一个位置来存储你决定在该变量中保存的任何值。对于 GML,这并不总是必要的,因为它取决于变量的 作用域(scope)。在你使用 GameMaker Studio 2 编程的时候,有四个主要的变量类别,每个都有自己的 作用域 (可以将其视为其操作区域或触及范围)。这些变量及其范围概述如下:

实例变量是在物体的实例中创建的,并且被认为是该实例的唯一变量 - 即:同一物体的许多实例可以具有相同的变量,但每个变量可以包含不同的值,因为它们对于每个实例都是唯一的。但一个实例变量是 怎样 创建的呢?好吧,只需为它们赋值,就可以创建新变量,如下面的小例子所示:

potions = 12;
life = 100;
name = "Jock MacSweeney";
strength = 5.5;
armour = -2;

正如你所看到的,你只需要提供名称,然后使用值(数字或字符串)来设置该变量,并准备好在你编码的物体的实例中使用它。然后可以在实例中以多种方式使用和修改这些变量,例如,此代码可能处于碰撞事件中,并用于从变量 “life” 中减去一定的量:

life -= 5 + armour;

如果 “life” 为 100,则它现在的值为 97(100 - (5 + -2) = 97)。现在,这是一个简单的例子,而你 可以 将 “armor” 替换为 -2 的实际值,但如果你在多个位置直接使用该值(即 -2)然后决定更改它,这样会发生什么?你将不得不找遍所有代码并将每个 -2 更改为新值,这是耗时且非常容易出错的!但是如果你使用一个变量,你所要做的就是重新分配一个新值,代码将从那时开始自动使用那个新值,使得事情变得更加灵活,如果出现问题则更容易修复。还应该注意的是,即使不打算改变一个值,也更容易记住一个名为 “life” 的变量意味着什么,而不仅仅是一个数字。

GameMaker Studio 2 还有一个 “内置” 实例变量的集合,你应该知道它们,因为你可能会将自己的一个实例变量命名为和它们相同的名称,或者希望拥有和那些同名的全局变量,所以你要知道为什么会出现错误。然而,它们很容易被发现,因为它们在代码编辑器中以不同的颜色显示,并且也会出现在底部的自动补全(auto-complete)栏中。

有几个函数旨在帮助你处理实例变量(主要用于导入的项目和拖放动作编码的 兼容性脚本,但它们也可以在其他地方使用):


局部变量是我们 为特定事件创建的变量,然后在 事件 已经完成的时候废弃它们(唯一的例外是脚本资源,其中 var 声明的变量只是在脚本本地的,离开脚本就会废弃)。我们为什么需要它们?好吧,变量会占用内存中的空间,可能我们只会将它们用于一个动作或函数(脚本),在这种情况下,我们只需要让它在内存中停留一个短暂的时间。这可以使你的代码库保持干净整洁,同时保持内存空间优化以满足真正需要的内容。要声明局部变量,我们使用该函数 var,就像这样:

var i, num, str;
i = 0;
num = 24.5;
str = "Hi there!";

上面创建的所有变量将在创建它们的事件(或脚本)的末尾被 “遗忘”(即:从内存中删除)。你必须注意你用 var 声明的临时变量名不能跟运行该段代码的物体中的其它变量名重复,还要确保你没有不小心在声明该变量的事件以外的事件中调用它。这些变量在程序中经常使用,特别是在用于计算迭代的循环中,或者在一个不会再次重复的操作中多次使用一个值的时候。这是另外两个例子:

var i = 0; // 译者注:创建局部变量并设置为 0
repeat (10)
   {
   inventory[i] = 0;
   i+=1;
   }

上面的代码创建了一个名为 “i” 的局部变量,并将其设置为 0,所有这些都在同一行中完成。请注意,在 GameMaker 以前的版本中,你必须首先声明局部变量,然后 再为它们分配值,但在这个版本中你可以声明 同时为它们分配一个值。然后,上面的代码中使用此变量初始化一个数组。除了当前这个,变量 “i” 不会用于实例中的任何其他函数,因此可以认为它的作用域是局部的。这是另一个例子:

var xx,yy;
xx = x - 32 +irandom(64);
yy = y - 32 +irandom(64);
instance_create_layer(xx, yy, "Effects", obj_blood);

在此我们使用局部变量来存储一些随机的坐标,然后我们用它来创建一个实例。在这个例子中,你可以看到我们使用这些变量并不是绝对必要的,但为了便于阅读和使用,我们这样做了。比起下面使用的代码,上面那里写得更清晰,更明显:

instance_create_layer(x - 32 + irandom(64), y - 32 + irandom(64), "Effects", obj_guts);

关于 var 声明的变量还有一件事应该注意...... 因为它们对于运行它们的事件是唯一的,所以它们也可以通过代码在任何其他实例中使用!这意味着我们可以在其他实例中通过使用 “with()” 结构,来使用这些变量设置和更改内容(关于这个,手册 GML概述 部分还有一个章节)。实际代码看起来就像这样:

var num = instance_number(obj_Enemy);
with (obj_Enemy)
   {
   if num>10 instance_destroy();
   }

上面的代码有效是因为 var 声明的变量包含在本地的 事件 (或脚本),不是实例,也不是游戏世界中,因此可以在任何物体的任何函数中使用,只要它在同一个代码块中即可。


全局变量的基本描述是,一旦声明,就不属于任何特定的实例,可以被所有位置访问(译者注:使用地方的运行顺序需要在声明之后)。就像局部变量一样,必须声明全局变量,但与局部变量不同,全局变量保留在内存中直到游戏结束。因此,你可以创建一个全局变量来跟踪(例如)玩家拥有的子弹数量,然后在游戏中的不同地方更新此变量。全局变量不属于任何特定实例,并且可以随时被所有实例访问、更改和使用,但对变量所做的任何更改都是 “全局” 的,因此使用该变量的所有实例都将受到更改的影响。我们来看一个例子:

global.food = 5;

我们通过先写 “global” 然后再写一个 “.” 来声明 “food” 变量,以此来告诉 GameMaker Studio 2 此变量现在是全局范围的变量。任何时候我们想要访问和更改这个变量只要用这个方法就可以了。因此,我们创建了一个名为 “food” 的新变量,并将其声明为全局变量。现在,任何实例都可以以任何方式使用和更改此变量,所有其他实例都将 “看到” 这个(译者注:看到这个 global.food)。例如,我们可能有与玩家碰撞的不同食物物体,在碰撞事件中有这样的代码:

global.food += 1;

我们还有另一个物体像这样绘制这个值:

draw_text(32, 32, "food = " + string(global.food));

使用全局变量,我们可以更改值并查看这些更改,这些反映到引用此变量的物体的所有实例中。你必须注意,同 局部 变量一样,全局变量命名时不要使用任何实例变量相同的名字,这会导致变量重叠的 bug,并且有时可能是难以调试的问题(译者注:可以采用特殊的命名法,比如全部大写,global.FOOD,特殊前缀,global.GL_food、global.FO_food、vFood、varFood、v_food)。通常,你应该有一个脚本或物体,它在游戏最开始时声明所有全局变量(例如,在房间开始事件 Room Start Event 中),以便全部初始化并准备好供任何其它实例使用,并且如果你需要检查变量名称,这也是为你提供一个方便的地方返回去参考。

GameMaker Studio 2 还有一个 “内置” 全局变量的集合,你应该知道它们,因为你可能将你的一个实例变量命名为相同的名字,或者希望拥有和它们同名的全局变量,所以你要知道为什么会出现错误!然而,它们很容易被发现,因为它们在代码编辑器中以不同的颜色显示,并且也会出现在底部的自动补全(auto-complete)栏中。大多数内置全局变量都是非常具体的,并且只会在极少数情况下使用 - 并且列在手册的相应部分中 - 但是有一个重要的并没有在其他地方列出:

要注意,还有三个 弃用的 内置全局变量,你也应该知道(这三个变量仅用于支持从 GameMaker 前代版本中导入的传统项目,请不要在新项目中使用):

以下方法也可用于声明全局变量,但它 仅用于向后兼容性 ,并且不建议你将此方法用作新项目,GameMaker Studio 2 将来的版本可能不会支持它。

创建全局变量的第二种方法是使用 globalvar 声明,就像你想要一个局部变量用 var 声明一样。

重要! 使用 globalvar 声明是一种 弃用 的方法,仅支持传统用途。你应该 总是 使用 global. 来标记全局变量的名称。


此(已弃用)声明将按如下方式使用:

globalvar food;
food = 5;

一旦以这种方式声明变量,“food” 现在将被认为是全局的,不再需要 global. 前缀 - 这也意味着在代码中识别全局变量要困难得多,这也意味着当你在不同的物体或者安装的插件中使用相同的变量名更容易产生变量重叠。以这种方式声明后,全局变量的访问方式如下:

food += 2;

或:

draw_text(32, 32, "food = " + string(food));

正如你所看到的,没有任何东西可以证明这个变量是全局的,你可能会在游戏中出现许多微妙的问题,这就是为什么应该避免使用这个声明的原因。

有几个函数旨在帮助你处理全局变量(主要用于导入项目和拖放动作编码产生的 兼容性脚本,但它们也可以在其他地方使用):


内置变量是“内置于”游戏世界中物体和房间中的特殊变量,它们可能是实例的或全局范围的(但 决不 是局部的变量)。上面的部分列出了一些全局变量,手册的精灵、房间、物体等不同部分也概述了可用的内置变量。这类内置实例变量的示例如下:

内置全局变量的示例如下:

内置变量可以像大多数其他变量一样进行更改和设置,有些甚至可以是数组,只是不需要像常规变量一样通过设置它们来创建它们,因为它们已经初始化为默认值。


虽然不完全是变量,但宏在使用方式上与变量类似,即:它们是一些命名的值,你可以在所有代码中使用它们,代替一些硬值(译者注:常数)。基本上,一个宏(像这样使用的时候也称为 常量)是一个命名变量,它包含一个作为常量的单个值(或字符串)。你可以使用 脚本编辑器 定义自己的常量,然后在你的代码和 DnD 中使用它们,就像它们是常规变量一样,唯一的区别在于它们的值 在游戏中无法改变。例如,假设你将定义以下的宏作为为一个常量(请注意前面的 “#”,以及末尾没有冒号“;”):

#macro total_weapons 10

然后你会在代码中调用它,如下所示:

if ++pos == total_weapons
   {
   pos = 0;
   }

请注意,你将无法更改常量值,因此这样的代码将导致游戏崩溃:

total_weapons = 11;

你可以在代码或脚本中的任何位置定义宏常量,它将会 预编译 并且从一开始就包含在你的游戏中,但我们建议你创建专用的脚本并在其中定义所有宏常量。以后组织和调试会更容易!

如果你需要在运行时更改宏的值,那么你应该将其设置为全局变量,因为这些可以在代码里更改,除非你将宏设置为 函数。通过将宏设置为函数,意味着每次使用宏时都会调用此函数。比如:

#macro col make_colour_hsv(irandom(255), 255, 255)

然后你会像这样调用宏常量:

image_blend = col;

使用此代码将在每次使用宏时,图像混合为不同的颜色。It is worth noting that you can also split macros over multiple lines using the forward slash \ to show where the line breaks. An example would be something like:

#macro hello show_debug_message("Hello" + \
string(player_name) + \
", how are you today?");

This is purely cosmetic, in that splitting a macro like this will have no effect over the result of the final macro, and is simply to provide support for multiline text on macros that have longer lines of code.

One very important feature of macros is that they can be defined for use with specific Configurations (configs), meaning you can have the same macro name but give it different values based on the currently selected config. For example, say you have a configuration for Android Ads and another for iOS Ads, then you could define a single macro to hold the required app ID value:

#macro ad_id "";
#macro Android:ad_id "com.yoyogames.googlegame"
#macro iOS:ad_id "com.yoyogames.appstoregame"

As you can see, you give the config name first then a colon : and then the macro name and value. Note that you cannot have any whitespace between the colon : and either the config name nor the macro name otherwise you will get an error.