with

正如在其他实例中处理变量一节所指出的,可以读取和改变当前执行任何给定代码的实例和结构中的变量值。然而,在很多情况下,你想做的事情远不止是在这些其他实例中改变一个变量,而且可能想执行更复杂的代码操作,需要多个函数和代码行。例如,想象一下,你想把你游戏中的一个球对象的所有实例向下移动8像素。你可能认为这只需通过下面这段代码即可实现。

obj_ball.y = obj_ball.y + 8;

但这是不正确的,因为赋值的右侧获取第一个球的 y 坐标值并加 8。接下来,这个新值被设置为所有球的 y 坐标,因此结果是所有球都获得相同的 y 坐标,即使您使用以下命令:

obj_ball.y += 8;

它将具有完全相同的效果,因为它只是第一个语句的缩写。那么我们如何实现这样的目标?这就是 GML 中存在with语句的原因。

句法

with语句采用以下形式:

with (<expression>)
{
    <statement>;
    <statement>;
    ...
}

表达式可以接受多个不同的输入:

然后,这会将大括号{ }内的代码的范围从执行with的实例、结构或函数更改为实例(或实例或结构))在表达式中给出。

一旦表达式设置了with的范围,就会针对每个指定的实例或结构执行语句,就像它是当前实例或结构一样(即self)。因此,回到我们最初的问题,要将球对象的所有实例向下移动 8 个像素,您可以输入:

with (obj_ball)
{
    y += 8;
}

如果你想执行多条语句,只需将它们包含在大括号中,就像你在任何其他代码块周围一样。因此,例如,要将示例中的所有球移动到随机位置并给它们随机速度和方向,您可以使用:

with (obj_ball)
{
    x = random(room_width);
    y = random(room_height);
    speed = 1 + random(2);
    direction = random(360);
}

 

注意房间中的实例是按照特定的顺序创建的,它们的创建事件也会在一个接一个创建时执行。这意味着您在从 创建事件中的其他实例读取变量时必须小心,因为其他实例可能尚未运行其 创建事件!

例如:假设 ObjectAObjectB 之前创建,并且您在这些对象的创建事件中有以下代码:

ObjectA 创建 -myValue = objectB.myValue;
ObjectB 创建 -myValue = 10;

首先创建 ObjectA 并运行其 Create 事件,然后导致游戏崩溃:

“变量 objectB.myValue(100003, -2147483648) 在读取之前未设置。”

这仅仅是因为 ObjectB 甚至还没有被创建,所以在它的 创建事件中初始化的任何变量都不存在。这就是为什么在 创建事件中引用其他类似实例时必须小心,包括在 with() 块内运行的任何代码。

 

作为循环

with语句本质上执行一个循环。根据表达式的结果,大括号{ }内的语句将根本不执行、执行一次或多次:

由于with表现为循环,因此您也可以在其中使用特殊的breakcontinue语句。使用break将立即退出with代码块,并在with完成后继续执行事件或函数中的任何代码,例如:

var _count = 0;
with (obj_enemy)
{
    if (++_count > 10)
    {
        break;
    }
    hp = 100;
}

上述代码循环遍历对象obj_enemy房间中的实例,并将找到的前 10 个实例的变量hp设置为 100。如果存在超过 10 个实例,with 代码将调用break并结束。

with 循环中使用 continue 的示例如下:

with (obj_enemy_parent)
{
    if (invulnerable == true)
    {
        continue;
    }
    hp -= 25;
}

此代码将循环遍历具有父级obj_enemy_parent的所有实例,然后检查每个实例以查看invulnerable实例变量是否为true。如果是,则continue关键字结束循环的当前迭代并移至下一个可用实例,否则它会从hp变量中删除 25。这将重复,直到检查了具有该父级的所有实例。

具体用途

"其他"实例或结构

如上所述,在语句内,指示的实例或结构已成为运行代码块的目标(self) 实例,这意味着原始实例(包含with和整个代码块)已成为其他实例。

因此,例如,要将所有球移动到实际包含代码的当前实例的位置,您可以键入以下内容:

with (obj_ball)
{
    x = other.x;
    y = other.y;
}

在新实例中执行代码

with (instance_create_layer(x, y, "Instances", obj_ball))
{
    speed = other.speed;
    direction = other.direction;
}

上述代码将创建一个obj_ball实例,并为其分配运行整个代码块的实例的速度和方向。

提示如果您只需为新实例的变量赋值,您可以通过instance_create_layer/instance_create_depthvar_struct参数传递它们。

实例检查函数

相当多的 实例函数 返回 对象实例noone。这使得它们可以方便地与 with 语句结合使用:

with (instance_nearest(x, y, obj_ball))
{
    instance_destroy();
}

上述代码将销毁距离运行该代码的实例最近的 obj_ball 实例。如果未找到实例,该函数将返回 noone,以便不执行 with 内的代码。

赋值结构体变量

with(clone_struct)
{
    xx = other.x;
    yy = other.y;
    spd = other.speed;
    dir = other.direction;
}

上面的代码使用 with 来定位结构并将给定的结构成员变量设置为存储在调用代码的实例的实例变量中的值。

跨作用域的局部变量

var _inst = noone;
with (obj_ball)
{
    if (str > other.str)
    {
        _inst = id;
    }
}
if (_inst != noone)
{
    target = _inst;
}

由于使用了 局部变量 ,上述代码比之前的代码稍微复杂一些。该变量是 事件脚本 函数本地 变量,而不是实例或结构的本地变量,因此可由在该变量中引用的所有实例使用和访问代码块。因此,在上面的代码中,我们将局部变量设置为特殊关键字 noone,然后使用 with 构造让 obj_ball 的每个实例检查其 str 变量运行代码块的实例的值。如果变量的值较大,则它们会将其唯一 ID 存储在 inst 局部变量中,这意味着在代码末尾,仅存储值大于调用实例 (或关键字 noone( 如果没有更大的) 将存储在局部变量 _inst 中。