语言特性


GameMaker 语言(GML)有许多功能特性,这些功能是使用该语言编写的所有代码的基本结构的组成部分。它们之中的每一个在你编写任何代码时都会多次使用,因此在开始编写项目之前,你应该知道它们是什么以及它们是如何工作的。

A simple "if" statement takes this form:

if (<表达式>) <语句>


or it can have the slightly more complex "if... else..." form:

if (<表达式>) <语句1> else <语句2>


在这个情况下,对这个表达式进行求值,并且如果(近似)值是 <= 0(false),语句后面的 else 被执行,否则(true)是另一个语句被执行。经常在 if 后面的语句加上花括号是一个好习惯,并且为每一个语句另起一行,所以最终代码将会有这种格式:

if (<表达式>)
   {
   <语句>
   ...
   <语句>
   }
else
   {
   <语句>
   }


作为一个小例子,想一想下面的代码,哪一块将移动实例朝着 x = 200 移动:

if (x < 200)
   {
   x += 4;
   }
else
   {
   x -= 4;
   }


请注意,你也可以这样在一个 if 中做复合检查,即:检查同一语句中的各种值或表达式。当你这样做, GameMaker Studio 2 将逐个求值,如果其中任何一个值为 false,则将跳过其余部分。比如:

if (keyboard_check_pressed(vk_enter) && !instance_exists(obj_Player)) // 同时检测两个
   {
   go = false
   alarm[0] = room_speed // 设置计时器为房间速度
   }


你也可以使用 条件运算符 (也叫作 三元 运算符),这实际上是执行一个最基本的 “if” 的“快捷方式”。它具有以下语法:

变量 = 条件语句 ?<表达式 1 (条件为 )返回表达式 1 的值> : <表达式 2 (条件为 返回表达式 2 的值)>


条件运算符将返回两个给定值中的一个,具体取决于条件计算结果是 还是 , 例如:

var temp_x = x < (room_width / 2) ?32: room_width - 32;


上面的代码将检查 “x” 的值与房间宽度一半的值,然后如果是小于,它将 “temp_x” 设置为 32,否则 “temp_x” 将是 room_width - 32。以下是更多的使用示例:

draw_text(x, y, "The fee is " + (global.Member ?"$2.00" : "$10.00")); // 判断是不是会员,然后返回不同的费用
path_start((global.level > 10 ?path_hard : path_easy;), 2, path_action_reverse, true); // 判断等级再决定难度
--hp <= 0 ?instance_destroy() : score += 10; // 判断血值决定销毁还是得分


值得注意的是,你可以嵌套条件运算,但如果你这样做,每个操作都需要括在括号中,例如:

var c = a ?"foo" : (b ?"bar" : "wii"); // 正确的方式
var c = a ?"foo" : b ?"bar" : "wii";   // 会出错


A "repeat" statement has the form

repeat (<表达式>) <语句>


语句被重复执行的次数由表达式的近似值指定。比如说,下面的程序将会在随机位置创建 5 个球。

{
repeat (5) instance_create_layer(random(400), random(400), "Instances", obj_ball);
}


这对于规避键入多次相同的代码、或者使用数组的情况、又或者一些运算中的计数等等是非常有效的。比如:

{
var i, total;
i = 0;
total = 0;
repeat (10)
   {
   total += array[i];
   i += 1
   }
draw_text(32, 32, total);
}


A "while" statement has the form

while (<表达式>) <语句


只要表达式是 true,这些语句(也可以是语句块)就会被反复执行。请小心你的 while 循环! 它很容易就能造成无限循环,在这种情况下你的游戏将被挂起并且不再响应任何的用户输入。下文你能找到一个典型使用 “while” 循环方式的一个范例:

{
while (!place_free(x, y))
   {
   x = random(room_width);
   y = random(room_height);
   }
}


上面的程序试图将当前物体放置在空闲位置(这与将物体移动到随机位置的操作大致相同)。

A "do" is really the "do... until" statement as you cannot have one without the other. 结构如下:

do <语句> until (<表达式>)


语句(也可以是代码块)会一直运行直到表达式为真,至少会运行一次。注意这是一个循环,可能会出现死循环,游戏将会挂起不响应输入。Below you can find an example of a typical way to use "do... until":

{
do
   {
   x = random(room_width);
   y = random(room_height);
   }
until (place_free(x, y)); }


上面的程序试图将当前物体放置在空闲位置(这与将物体移动到随机位置的操作大致相同)。

A "for" statement has this form:

for (<assigment1> ; <expression> ;<statement1>) <statement2>


This works as follows - First assigment1 is executed, then the expression is evaluated and, if it is true, statement2 is executed. Then statement1 is performed and then the expression is evaluated again. 如此循环,直到表达式为假。

现在,这样写的可能听起来很复杂,但你应该把它解释为:

  • 语句 1 初始化,用于循环。
  • 表达式测试是否结束循环。
  • 表达式 2 是 “步” 语句,递进一个值进入下一个循环。
这个循环在处理包含很多行代码的重复任务极其 有用,通常用来数组计数,或者绘制。下面的代码解释这种语句的典型用法:

{
for (var i = 0; i < 10; i += 1)
   {
   draw_text(32, 32 + (i * 32), string(i) + ". "+ string(scr[i]));
   }
}


The above code initialises a for loop, starting at 0 and counting up until 9, and then uses the loop value of "i" to draw the values stored in an array down the screen. Note how the "for" loop variable "i" is used to not only loop through the array, but to draw a number as well as tell GameMaker Studio 2 where to draw the values to in the room. This flexibility is one of the main reasons why "for" loops are so important in programming.

很多情况下,你想要实例根据一个特定值完成一个动作。You can do this using a number of consecutive "if" statements but when the possible choices gets above two or three it is usually easier to use the "switch" statement. A switch statement has the following form:

switch (<表达式>)
{
case <expression1>: <statement1>; ... ; break;
case <expression2>: <statement2>; ... ; break;
...
default: <statement>;
}


工作流程如下:

  • 首先括号里面的运行表达式
  • 下一步与 case 后面的表达式的结果进行比较。
  • 遇到第一个正确的值后运行会继续,直到出现一个 “break” 语句。.
  • 如果没有一个 case 表达式正确,将会在 “default” 语句后继续(default 并非必须,那时没有动作执行)。
注意,同一个语句前可以出现多个 case。同样,“break” 也不是必须的,没有它时,简单的继续到下一个 case 语句。这意味着你可以创建一个分层的 “开关”,根据输入值运行不同部分的代码。here is an example of a typical "switch" from a game:

{
switch (keyboard_key)
   {
   case vk_left:
   case ord("A"):
      x -= 4;
      break;
   case vk_right:
   case ord("D"):
      x += 4;
      break;
   case vk_up:
   case ord("W"):
      y -= 4;
      break;
   case vk_down:
   case ord("S"):
      y += 4;
      break;
   }
}


The above code uses "switch" to check for a keyboard event and then compares that to the cases listed. 遇到了适当的条件后运行相应的代码。Note how in the code we have used the way that "switch" can check multiple cases and continue if no break is encountered to permit various keys to be used to get the same result. 这仅是允许多种设置处理移动的一种方法。

The "break" statement is used to end prematurely a for, repeat, while, do... until loop of some kind, or to tell a switch statement to end at that point, or to prematurely end a with call. 下面可以看到一些用法的例子,语法挺简单的:

break;


for 循环中的 “break”:

{
var i;
for (i = 0; i < 10; i += 1)
   {
   if array[i] = 234 break;
   }
num = i;
}


repeat 循环中的 “break”:

{
var i, temp;
i = 0;
temp = 0;
repeat (10)
   {
   temp += array[i];
   if temp > max_total break else i += 1;
   }
}


while循环中的 “break”:

{
var i;
i = 0;
while (!place_free(x, y))
   {
   x = random(room_width);
   y = random(room_height);
   if i > 50 break else i+=1;
   }
}


"break" when using with:

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


The "continue" statement has the form:

continue


If used inside of a statement that forms a loop (repeat, while, do... until or for), it will immediately jump back to the beginning of the loop as if the loop had run through and looped back. It will also do the same when using the with function, where it will cause the code to skip to the next instance and run again, but if used outside of context it will give an error.

Below is an example of its use in a "for" loop:

{
var i;
for (i = 0; i < 10; i += 1)
   {
   if array[i] = "" continue;
   array[i] = "";
   }
}


上述代码将判断 array[i] 的值,如果是一个空字符,它将跳回到循环的开始。

The "exit" statement has the form:

exit;


“Exit” 简单的结束当前脚本或事件的运行。请注意,这里的使用略有不同,具体取决于范围:如果你在脚本中使用 exit,它将简单地退出脚本并返回到调用脚本的代码位置,但是如果在物体内的代码块中使用,它将退出 整个事件,即使在调用函数的位置后面还存在各种单独的代码块。通常它是用来避免运行一个特定的代码块,比如碰撞事件。下面的代码给出了一个简单的例子:

{
if !visible exit; // 如果物体是隐藏的就退出
while (place_meeting(x, y)) // 检查对应位置的碰撞
   {
   x -= lengthdir_x(1, direction - 180); // 掉个头
   y -= lengthdir_y(1, direction - 180);
   }
}


上面的代码检查一个变量,如果它为真(true),就退出代码块,否则它继续执行并运行其余的代码。
注意:它不是结束游戏的运行。如果要那样,你需要使用 game_end 函数。

访问其它实例的变量这一节所说,可以在其他实例中读取和更改变量的值。但在许多情况下,你希望能做更多,而不仅仅更改其它实例的一个变量。例如,假设你想要将游戏中的所有球物体(ball objects)移动 8 个像素。你可能认为只需通过以下代码即可实现:

obj_ball.y = obj_ball.y + 8;


但这是不正确的,因为赋值的右侧只是获得第一个球的 y 坐标值并将其加 8。接下来,这个新值将设置为所有球的 y 坐标,因此结果是所有球都得到了相同的y坐标,就如同下面的写法:

obj_ball.y += 8;


它具有完全相同的效果,因为它只是第一个语句的简写。所以怎样 才能实现呢?For this purpose there exists the with statement in GML. 它的整体结构是:

with (<表达式>) <语句>


<Expression> indicates one or more instances, and for this you can use an instance id, the name of an object (which indicates all instances of this object are to run the code block) or one of the special keywords (all, self, other). <语句> 现在让指定实例的每一个运行一次代码,就像这个实例是当前(self)的实例一样。因此,要将球物体的所有实例向下移动 8 个像素,你可以键入:

with (obj_ball) y += 8;


如果要执行多个语句,和任何其它程序一样,请在它们周围加上大括号。例如,要将所有球移动到随机位置,你可以使用:

with (obj_ball)
   {
   x = random(room_width); // 获取房间的随机位置
   y = random(room_height);
   }


Note that, within the statement(s), the indicated instance has become the target (self) instance that runs the code block, which means that the statements the original instance (that contains the "with" and the code block) has become the other instance. 例如,要将所有球移动到当前实例的位置,你可以键入:

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


with 语句是一个非常强大的工具,在很多很多情况下都很有用,所以你必须完全理解如何去使用它。为了帮助你,下面还有一些使用示例:

with (instance_create_layer(x, y, "Instances", obj_Ball)) // 新创建一个球实例
   {
   speed = other.speed; // 让它的速度与 with 语句所在实例的速度一致
   direction = other.direction;
   }


上面的代码将创建一个 obj_Ball 实例,并为其指定一个速度和方向,使用的是运行整个代码块的实例的值(译者注:也就是 with 外面的实例)。

with (instance_nearest(x, y, obj_Ball)) //with 所在实例相临的一个 obj_Ball 实例
   {
   instance_destroy(); // 销毁实例
   }


上面的代码将破坏与 with 所在实例最近的 obj_Ball 实例。

var inst;
inst = noone;
with (obj_ball)
   {
   if str > other.str inst = id; // 比较两个值,获取这个实例的 id
   }
if inst != noone target = inst; // 对于符合条件的实例,赋值给 target 变量


上面的代码比之前的代码稍微复杂了一些,因为使用了局部变量。这个变量相对于 脚本 是本地的,而不是相对于实例,因此可以被代码块中引用的所有实例使用和访问。So, in the code we have set it to the special keyword noonett> and then use the "with" construction to have every instance of obj_Ball check their "str" variable against that of the instance running the code block. If the value of the variable is larger, then they store their unique id in the "inst" local variable, meaning that at the end of the code, only the instance with a value greater than the calling instance (or the keyword noone if none are larger) will be stored in the local variable inst. For more information on local variables see the section Variables And variable Scope.

The "return" statement has the form:

return (<表达式>)


你只能在 脚本 中使用 return 语句,它用于从脚本返回一个值,以便在之后的代码或脚本调用中使用。应该指出的是 脚本的运行在 return 语句处结束,意味着在脚本中 return 语句之后的任何代码都不会被运行。这是一个简短的示例脚本 “scr_sqr”,它计算传递给它的任何值的平方,并且它包含错误捕获,以防传递的参数不是实数:

{
if !is_real(argument0) // 如果不是实数
   {
   return 0; // 在这里跳出
   }
else
   {
   return (argument0 * argument0); // 返回正常的平方值
   }
}


在一个代码块中调用脚本就像调用函数一样 - 使用脚本的名字,括号内填写参数的值。所以,上面的脚本这样使用:

if keyboard_check_pressed(vk_enter) // 检测回车键
   {
   val = scr_sqr(amount); // 传递变量 amount,返回值赋值给 val
   }