Structs & Constructors

struct (结构) 是保存其他变量集合的变量。结构所持有的变量可以是前面提到的任何数据类型,这些变量可以在初始结构声明之后读取和写入,也可以在声明结构之后向其添加更多变量。

注意 结构体和结构体之间存在细微差别。 构造函数对象和实例 。请参阅 构造函数和结构与对象和对象实例 了解更多信息。

结构体中使用的变量应遵循通常的变量命名方案,即:它们不能以数字开头,只能由字母数字字符和下划线"_"字符组成,还要注意结构体的内容是独立于创建它的实例或函数,因此,如果您愿意,您可以使用内置变量名称,例如image_indexxy

在初始创建之后,虽然结构会占用内存空间,但它们在存在时没有处理开销。结构语法如下:

<variable> =
{
    <variable> : <value>,
    <variable> : <value>,
    etc...
};

因此,实践中的一个例子是:

mystruct =
{
    a : 20,
    b : "Hello World"
};

以上代码在变量mystruct中创建实例范围结构,并使用某些值填充该结构(可以在局部、实例和全局范围内创建结构,就像任何其他变量一样-有关详细信息,请参阅变量和变量范围一节)。请注意,最初创建结构时不必填充其内容,只需执行以下操作即可创建空结构:

mystruct = {};

然后可以在游戏代码的稍后一点将变量添加到此结构中。若要在以后添加它们,请使用点运算符(与其他实例中的寻址变量的方法相同):

mystruct.newItem = "Some Text";

这与初始化结构体的结果相同:

mystruct =
{
    newItem : "Some Text"
};

下面是一个具有各种变量和数据类型的结构示例:

var _xx = 100;
mystruct =
{
    a : 10,
    b : "Hello World",
    c : int64(5),
    d : _xx + 50,
    e : function(a, b)
        {
            return a + b;
        },
    f : [ 10, 20, 30, 40, 50 ],
    g : image_index
};

在上面的代码中可以看到,您可以在结构中定义方法和使用运行时函数,还可以在结构声明中使用局部变量和实例变量。

结构声明中的实例变量?

您会注意到在上面的示例中,结构变量 "g" 被设置为 image_index,它是一个实例变量。您可能认为在这种情况下需要使用 关键字 other 来获取实例变量,但这不是必需的。本质上,当您定义 struct 时,冒号 ":" 左侧的所有成员变量都是 struct 变量,右侧的值和变量使用任意范围( 在本例中是一个实例)。

让我们看一个简单的例子来说明这一点。假设您想要使用变量 "x" 和 "y" 定义一个结构体,并且希望将它们设置为定义该结构体的实例的 xy。实际上,代码如下所示:

mystruct =
{
    x : x,
    y : y
};

在上述代码中,结构体成员变量 xy 被设置为实例变量 xy 中保存的值,因为冒号右侧的 : 指的是定义结构体的实例。值得注意的是,这意味着您 不能 使用结构体成员变量来定义结构体声明中的后续变量。例如,以下内容会给您带来错误:

mystruct =
{
    a : 10,
    b : 10,
    c : a + b
}

发生错误的原因是变量 ab 实际上是在定义结构的范围内进行求值 (它们位于冒号 : 的右侧),并且 不是 结构体本身中定义的。

重要提示 您不能使用任何内置的 全局变量 作为结构体成员名称,例如:game_idfps。您可以在 结构禁止变量 上找到这些全局变量的完整列表。

同名变量初始化的简写

使用同名的现有变量(局部变量或实例变量)的值初始化的结构变量可以使用简写表示法编写。而不是写:

var a = 12;
b = 14;

mystruct = 
{
    a : a,
    b : b,
    c : 101
}

你可以这样写:

var a = 12;
b = 14;

mystruct = 
{
    a,
    b,
    c: 101
}

在这两种情况下,mystruct.a将被赋值为12,而mystruct.b将被赋值为14,因为它将在初始化结构的作用域中查找同名的变量。

访问结构变量

定义结构后,可以使用"点"表示法访问其中的数据,如下所示:

mystruct =
{
    a : 20,
    b : "Hello World"
}

mystring = mystruct.b + string(mystruct.a);

您还可以对结构中的变量执行操作,或者在函数中使用它们,就像对待任何其他变量一样。例如:

mystruct.a += 1;
mystruct.b = mystruct.a + 20;
mydir = point_direction(mouse_x, mouse_y, mystruct.xx, mystruct.yy);

最后,结构中可以嵌套其他结构,如下所示:

mystruct =
{
    a :
    {
        aa : "This is an example"
    },
    b :
    {
        bb : "And another one"
    },
};

要访问此类嵌套结构,仍需使用点符号,如下所示:

var _str = mystruct.a.aa + " " + mystruct.b.bb;
show_debug_message(_str);

访问结构中数据的另一种方法是使用 with 语句。例如,您可以这样做:

with (mystruct)
{
    a += other.x;
}

with 配合使用会将代码的作用域更改为给定的结构,您可以在其中操作结构作用域中的成员变量。请注意,在本例中,我们还使用了 other 关键字。这就像在实例中将 with 一起使用时一样,并将引用实际运行代码块的实例 (或结构)。

访问结构中数据的最后一种方法是使用结构访问器 $。这允许使用字符串访问结构变量,用于读取:

var _value = mystruct[$ "x"];

和写入:

mystruct[$ "x"] = 200;

如果你需要使用字符串来访问一个结构变量,那么获取它的hash并在read/write中使用它来访问变量会更快。

当不再需要某个结构体时,可以使用 delete 运算符将其从内存中删除,该运算符将该结构体标记为可以进行垃圾回收。这并不是严格要求的,因为如果您的代码中不再引用该结构, 垃圾收集器 可能会自动执行此操作,但这样做是一种很好的做法,我们建议您这样做 (例如,在 清理事件 中调用 delete 显式告诉垃圾收集器要删除实例范围结构)。这是一个例子:

// Create event
mystruct =
{
    pos_x : x,
    pos_y : y,
    count : 1000
};

// Clean Up event
delete mystruct;

构造函数

您还可以使用脚本函数方法来创建可用于生成新结构的函数,这需要使用函数的构造函数关键字,以及从此类函数创建结构时使用new运算符。请参阅以下函数:

function Vector2(_x, _y) constructor
{
    x = _x;
    y = _y;

    static Add = function(_vec2)
    {
        x += _vec2.x;
        y += _vec2.y;
    }
}

或者,使用方法变量语法:

Vector2 = function(_x, _y) constructor
{
    x = _x;
    y = _y;

    static Add = function(_vec2)
    {
        x += _vec2.x;
        y += _vec2.y;
    }
}

此处我们创建一个名为Vector 2的函数,并告诉GameMaker此函数用于通过在其定义后添加构造函数关键字来创建结构。然后可以这样调用此构造函数:

v2 = new Vector2(10, 10);

变量v2现在将包含带有变量xy以及static方法变量Add的结构。

您还可以在构造函数中使用可选参数:

function Vector2(_x = 0, _y = 0) constructor
{
    x = _x;
    y = _y;
}

如果在调用函数时未指定_x_y参数,则此构造函数现在将使用0。这意味着您可以创建新的 Vector2 结构,而无需指定任何参数:

empty_vector = new Vector2();

继承

以这种方式创建的函数还将支持单继承,即:您可以创建一个从另一个构造函数继承数据的构造函数。

注意 在使用继承时,不能使用方法变量来定义构造函数,只能使用脚本函数。

例如,我们在上面创建了 Vector2 构造函数,因此我们可以使用它作为另一个构造函数的“父”函数,我们将其命名为 Vector3:

function Vector3(_x, _y, _z) : Vector2(_x, _y) constructor
{
    z = _z;

    static Add = function( _vec3 )
    {
        x += _vec3.x;
        y += _vec3.y;
        z += _vec3.z;
    }
}

如您所见,在定义函数时,我们使用冒号":"将新构造函数与要从中继承的父构造函数分开。子构造函数(Vector 3)将_x_y参数传递到父构造函数(Vector 2)中,父构造函数用于首先运行父构造函数,然后执行子构造函数。这样,子构造函数就可以获取父项的变量(xy),并且还可以定义自己的变量(z)。

您还可以将值传递给父构造函数,以便某个子构造函数始终向其父构造函数提供相同的值:

function item(damage) constructor
{
    my_damage = damage;
}

function basic_sword() : item(10) constructor {}

var _basic_sword = new basic_sword();
show_debug_message(_basic_sword.my_damage); // Prints 10

这意味着BasicSword的damage将始终为10,因为它将该值传递给其父构造函数,而不管其自身的参数是什么。

请注意,为子构造函数中的参数指定缺省值将覆盖该参数的父级缺省值。请参见以下示例:

function parent(value = 10) constructor
{
    show_debug_message(value);
}

function child(value = 20) : parent(value) constructor
{
    show_debug_message(value);
}

var _child = new child();

这两个构造函数都将向输出日志打印20,因为这是子构造函数设置的参数的默认值,并且已将相同的值传递到父构造函数。

有关newdelete运算符的详细信息,请参阅以下页面:

检查构造函数继承

您可以使用 is_instanceof 来检查结构是否属于给定的构造函数,或者是否将该构造函数作为父构造函数。

function item() constructor {}

function potion() : item() constructor {}

function enemy() constructor {}

var _potion = new potion();

show_debug_message(is_instanceof(_potion, potion)); // true (1)
show_debug_message(is_instanceof(_potion, item)); // true (1)
show_debug_message(is_instanceof(_potion, enemy)); // false (0)

上面的代码显示了三个构造函数:itempotion,它是 item 的子类,以及 enemy,它是一个单独的构造函数。

它从 potion 构造函数创建一个结构。使用 is_instanceof(),我们可以检查新结构:

通过这种方式,您可以检查结构是否"是"某物,因为 potion 是 item,因此它为两个构造函数返回 true。然而,如果不是真的,反过来: item 不是 potion

该功能利用了"静态结构"。请参阅:静态结构

字符串输出

关于结构的最后一件事是,您可以更改结构输出到控制台的内容以进行调试。默认情况下,在结构体上调用函数 show_debug_message 将输出该结构体的内容 (如上所示)。不过,可以通过向名为 toString 的结构添加专门命名的方法来自定义此消息:

mystruct =
{
    a : 20,
    b : "Hello World",

    toString : function()
    {
        return "This struct says " + b + ", " + string(a) + " times!";
    }
}
show_debug_message(mystruct);

现在,当调用show_debug_message函数时,将使用toString方法生成输出,并且通过上面的示例,您将得到:

This struct says Hello World, 20 times!

请注意,您还可以对结构体引用调用string函数,并使用该函数将内容(或toString方法)显示到屏幕上,或者将其保存到文件中,或者无论如何,例如:

var _str = string(mystruct);
draw_text(32, 32, _str);

构造函数 和 结构与对象 和 对象实例

构造函数和结构与对象和实例类似,因为它们也包含可以执行的变量和方法。然而,它们之间有一些重要的区别:

结构体函数

最后,您可以在结构上使用许多运行时函数来获取它们包含的变量以及其他一些内容。您可以在结构函数下找到它们。