译:Gamemaker Studio 2.3 语法详解

作者:highway★
2021-12-10
10 7 3

作者:Vadim(aka YellowAfterlife)
译:highway★

原文链接

译注:从 2.3 更新之后基本没怎么碰过 GMS2,重新开启 GMS2.3 之后很多新加的东西都没咋看过对很多新东西有些恐惧,总是一拖又拖,这篇文章讲的很细致,比起看视频也节省一些时间。搬运过来,希望能对同样使用 GMS2,特别是我这种对 2.3 比较懵逼的人有些帮助。

Chained accessors(链式访问器)

长期以来,GameMaker 一直允许少量的 "访问器 ",

// 正常 array 操作:
val = an_array[index];
an_array[index] = val;
// 非写入时复制操作(non-copy-on-write):
an_array[@index] = val; // 等同于 array_set(an_array, index, val)
// ds_map:
val = a_map[?key]; // 等同于 val = ds_map_find_value(a_map, key)
a_map[?key] = val; // 等同于 ds_map_set(a_map, key, val)
// ds_list:
val = a_list[|index]; // 等同于 val = ds_list_find_value(a_list, index)
a_list[|index] = val; // 等同于 ds_list_set(a_list, index, val)
// ds_grid:
val = a_grid[#x, y]; // 等同于 val = ds_grid_get(a_grid, x, y)
a_grid[#x, y] = val; // 等同于 ds_grid_set(a_grid, x, y, val)

GMS 2.3 这些基础上稍作了扩展,允许将它们链接在一起--所以我们现在可以这样写:

list_of_maps[|i][?"hi"] = "hello";

来替代以前的写法:

ds_map_set(ds_list_find_value(list_of_maps, i), "hi", "hello");

对于嵌套数据结构和多维数组,这么写很方便。

Array 的改动

2D 数组现在只是嵌套的 1D 数组,你可以更容易地创建更高维数的数组。

array_1d[0] = "hi!"; // 没有改动
array_2d[1][0] = "hi!"; // 以前这么写 array_2d[0, 0] = "hi!"
array_3d[2][1][0] = "hi!"; // 新加的!
// ...等等

Structs

Structs 就像实例(instance),但没有任何事件或内置变量。非常轻便。

我们可以通过使用 {} 来创建一个空结构。

var q = {};
show_debug_message(q); // {  }
q.hi = "hello!";
show_debug_message(q); // { hi : "hello!" }
q.one = 1;
show_debug_message(q); // { hi : "hello!", one: 1 }
你也可以通过指定名称预先填入一些字段 name: value:
var q = { a: 1, b: 2 };
show_debug_message(q); // { b : 2, a : 1 }
q.c = 3;
show_debug_message(q); // { c : 3, b : 2, a : 1 }

array 类似,Structs 由 GMS2 自动管理,这意味着你不必像对待实例那样明确地销毁它们。

Structs 可以像之前我们在实例上那样的用法一样,比如我们可以 with(a_struct),尽管我们不能以这种方式遍历 struct 中的每一个 "实例"--我们需要将它们添加到一个 arraylist 中。

Structs as maps

与实例类似,structvariable_struct_* 函数用于动态管理其变量。

这使得 struct 可以作为 ds_maps 的垃圾收集替代物:

var q = { a: 1 };
variable_struct_set(q, "b", 2);
variable_struct_set(q, "$", "dollar");
show_debug_message(q); // { $ : "dollar", a : 1, b : 2 }
show_debug_message(q.b); // 2
show_debug_message(variable_struct_get(q, "a")); // 1
show_debug_message(variable_struct_get(q, "$")); // dollar

为了方便,2.3.1 开始通过添加 struct[$key] 访问器进一步扩展了这一点:

var q = { a: 1 };
q[$"b"] = 2; // 等同于 variable_struct_set(q, "b", 2)
var v = q[$"b"]; // 等同于 variable_struct_get(q, "b")

结合 array,这允许复制大多数数据结构而无需明确的销毁它们。

一些注意事项:

  • 直接 (a.b) 读/写比使用 variable_struct_* 函数更快,可用于您确定结构具有变量的情况。否则 variable_struct_* 函数的性能与 ds_map 非常相似。
  • ds_map 不同,ds_map 几乎可以接受任何 key 值,但 struct 变量名称是字符串,因此  variable_struct_set(q, 4, "four")variable_struct_set(q, "4", "four") 相同。
  • Structs for JSON
  • 2.3.1 增加了 json_stringifyjson_parse 函数,它们与现有的 json_encodejson_decode 很相似,但使用的是 structarray,而不是像之前的和 maplist

我们可以这样:

var o = {
    a_number: 4.5,
    a_string: "hi!",
    an_array: [1, 2, 3],
    a_struct: { x: 1, y: 2 }
};
show_debug_message(json_stringify(o));

这会输出下面的信息:

{ "a_string": "hi!", "an_array": [ 1, 2, 3 ], "a_struct": { "x": 1, "y": 2 }, "a_number": 4.5 }

并将该字符串传递给 json_parse 会返回给我们一个嵌套 struct

Functions

以前,每个脚本资源都将包含在调用时要运行的单个代码片段。

像下面这样:

/// array_find_index(array, value)
/// @param array
/// @param value
var _arr = argument0;
var _val = argument1;
var _len = array_length_1d(_arr);
for (var _ind = 0; _ind < _len; _ind++) {
    if (_arr[_ind] == _val) return _ind;
}
return -1;

但现在,情况不同了 - 我们可以在同一个脚本资源中有多个独立的片段,通过使用 function () {} 语法来区分:

/// @param array
/// @param value
function array_find_index() {
    var _arr = argument0;
    var _val = argument1;
    var _len = array_length_1d(_arr);
    for (var _ind = 0; _ind < _len; _ind++) {
        if (_arr[_ind] == _val) return _ind;
    }
    return -1;
}

/// @param array
/// @param value
function array_push() {
    var _arr = argument0;
    var _val = argument1;
    _arr[@array_length(_arr)] = _val;
}

其工作原理如下:

脚本中的 function name(){} 成为一个全局函数,这相当于 2.3 之前的工作方式

function name() {    // code here

}

function(){} 可以用作表达式,允许您执行

explode = function() {
    instance_create_layer(x, y, layer, obj_explosion);    instance_destroy();
}

Create 事件中,甚至将其用作函数调用中的参数!

layer_script_begin("MyLayer", function() {
    shader_set(sh_brightness);
    shader_set_uniform_f(shader_get_uniform(sh_brightness, "u_bright"), 1);
});
layer_script_end("MyLayer", function() {
    shader_reset();
});

在另一个函数中/在脚本外的 function name(){} 等效于:

self.name = function(){};

可以更方便使用。


任何在脚本内但在函数外的其他代码都将在游戏启动时运行;获取/设置变量将像 global.variable 一样工作:

show_debug_message("Hello!"); // 在创建任何实例之前显示
variable = "hi!"; // sets global.variable
// ...函数定义

允许它被用于任何初始设置。

然而,请注意,这个程序在进入第一个房间之前就已经运行了,所以,如果你想生成实例,你会想使用  room_instance_add

作为一个令人愉快的奖励,你现在可以不用 script_execute 来调用存储在变量中的函数。

function scr_hello() {    
    show_debug_message("Hello, " + argument0 + "!");
}
/// ...
var hi = scr_hello;
script_execute(hi, "you");
hi("you"); // 新的! 与上面效果一样

现在,开始进行更有趣的补充。

命名参数

函数语法的引入还带来了另一个奇妙的补充--命名的参数!

以前,咱得这么写:

function array_push() {
    var _arr = argument0, _val = argument1;
    _arr[@array_length(_arr)] = _val;
}

或者

function array_push() {
    var _arr = argument[0], _val = argument[1];
    _arr[@array_length(_arr)] = _val;
}

现在咱只需要这么写:

function array_push(_arr, _val) {
    _arr[@array_length(_arr)] = _val;
}

这使得可选参数也更容易--任何没有提供给脚本的命名参数都将被设置为未定义,这意味着咱可以这样写:

function array_clear(_arr, _val) {
    if (_val == undefined) _val = 0;
    // 之前得这么写: var _val = argument_count > 1 ? argument[1] : 0;
    var _len = array_length(_arr);
    for (var _ind = 0; _ind < _len; _ind++) _arr[@_ind] = _val;
    return _arr;
}

静态变量

这些变量类似于 C++ 中的局部静态变量。

也就是说,静态变量是持久的,但只在它所声明的函数中可见。

这对任何需要函数特定状态的情况来说都是很好的。

function create_uid() {
    static next = 0;
    return next++;
}
function scr_hello() {
    show_debug_message(create_uid()); // 0
    show_debug_message(create_uid()); // 1
    show_debug_message(create_uid()); // 2
}

静态变量在执行中第一次到达时被初始化。

function scr_hello() {
    // show_debug_message(some); // error - not defined
    static some = "OK!";
    show_debug_message(some); // "OK!""
}

因此,静态变量通常位于其各自函数的开头。

Methods/function 绑定

这个功能与基于 ECMAScript 语言中的 Function.bind 完全相同。

一个函数可以被 "绑定 "到某个东西上,这将在该函数调用中把自己变成那个值,把原来的自己推到其他地方(就像  with 语句那样)。

这意味着,如果你有

// obj_some, Create event
function locate() {
    show_debug_message("I'm at " + string(x) + ", " + string(y) + "!");
}

, 你可以同时进行

var inst = instance_create_depth(100, 200, 0, obj_some);
inst.locate(); // 100, 200
var fn = inst.locate;
fn(); // also 100, 200!

因为你得到的函数引用是与该实例绑定的。

一个函数可以被绑定到一个 struct、一个实例 ID,或者什么都没有(未定义)。

没有绑定到任何东西的函数会像 2.3 之前的脚本那样保留 self/other

然而,如果一个函数没有被绑定到任何东西上,但你以 some.myFunc 的形式调用它,它将被当作被绑定到 some  上。

自动绑定的工作原理如下:

  • 在脚本中的 function name(){} 不绑定任何东西,保持与 2.3 之前版本的兼容性。
  • 绑定到 selffunction name(){} 使得实例方法的定义更加简单(也就是说,你可以在 Create 事件中拥有一系列的函数定义)。
  • static name = function(){} 也没有绑定任何东西,这很好,因为你不希望静态函数绑定到父函数被调用的第一个实例。
  • 任何其他使用 name = function(){} 的行为都会被绑定到 self

函数可以使用方法内置函数进行[重新]绑定。一个已经被绑定的函数(function)在形式上被称为 "方法(method)"(因此被称为内置函数)。

总的来说,这不仅对实例/结构特定的函数很方便,而且还可以 "创建 "与一些自定义上下文绑定的函数

例如,你可以写一个函数,返回一个生成增量 ID 的函数(就像前面提到的的 static),并且让每个这样的返回函数的 ID 是独立的。

function create_uid_factory() {
    var _self = { next: 0 };
    var _func = function() { return self.next++; };
    return method(_self, _func);
}
//
var create_entity_uid = create_uid_factory();
var create_network_uid = create_uid_factory();
repeat (3) show_debug_message(create_entity_uid()); // 0, 1, 2
show_debug_message(create_network_uid()); // 0

函数调用

由于现在函数可以存储在任何地方,你也可以从任何地方调用它们。

scr_greet("hi!"); // 跟以前一样
other.explode(); // 这样可以
init_scripts[i](); // 这样也可以
method(other, scr_some)(); // 对'other'执行'scr_some',不用加'with'

内置函数引用

可以这样

var f = show_debug_message;
f("hello!");

而且我们可以自动为内置函数建立索引。

var functions = {};
for (var i = 0; i < 10000; i++) {
    var name = script_get_name(i);
    if (string_char_at(name, 1) == "<") break;
    functions[$name] = method(undefined, i);
    show_debug_message(string(i) + ": " + name);
}
// `functions` now contains name->method pairs 

这会输出:

0: camera_create
1: camera_create_view
2: camera_destroy
...
2862: layer_sequence_get_speedscale
2863: layer_sequence_get_length
2864: sequence_instance_exists

索引对于调试和脚本工具来说是非常方便的--例如,GMLive 现在使用这种机制,而不是有一个充满脚本的庞大文件来包装每一个已知的内置函数。

Constructor(构造函数)

Constructor 是一个标有 Constructors 后缀关键字的函数。

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

这使你能够做到

var v = new Vector2(4, 5);
show_debug_message(v.x); // 4
show_debug_message(v); // { x: 4, y: 5 }

简而言之,new 关键字可以自动创建一个空结构,为它调用构造函数,然后返回它。就像其他编程语言中的类一样! 但还有更多。

Static variables 静态变量

GMS2 将把 constructor 中的静态变量视为存在于由它创建的 struct 实例中,只要 struct 实例没有覆盖该变量。

这类似于变量定义对对象的作用,或原型在其他编程语言中的作用(如 JavaScript 原型或 Lua 的元数据)。

这可以用于默认值(然后可以覆盖),但最重要的是,可以向 struct 添加 method,而不需要在每个 struct 实例中实际存储:

function Vector2(_x, _y) constructor {
    x = _x;
    y = _y;
    static add = function(v) {
        x += v.x;
        y += v.y;
    }
}
// ... 然后
var a = new Vector2(1, 2);
var b = new Vector2(3, 4);
a.add(b);
show_debug_message(a); // { x : 4, y : 6 }

注意:如果您想在 constructor 中直接覆盖静态变量(而不是在其中的 function 中),您需要使用 self.variable 来区分 static variablenew struct 的变量:

function Entity() constructor {
    static uid = 0;
    self.uid = uid++;
}

(这将给每个实体一个唯一的 ID)

Inheritance(继承)

一个 constructor 可以使用: Parent() 语法从另一个 constructor 继承:

function Element(_x, _y) constructor {
    static step = function() {};
    static draw = function(_x, _y) {};
    x = _x;
    y = _y;
}

function Label(_x, _y, _text) : Element(_x, _y) constructor {
    static draw = function(_ofs_x, _ofs_y) {
        draw_text(_ofs_x + x, _ofs_y + y, text);
    };
    text = _text;
}

这将首先调用父 constructor,然后再调用子 constructor

在子 constructor 中定义的静态变量优先于在父 constructor 中定义的静态变量,这就为覆盖父字段提供了一种方法--因此,用上述方法,你可以做到

var label = new Label(100, 100, "Hello!");

label.step(); // 调用父 step 函数

label.draw(5, 5); // 调用子 draw 函数

如果你确实需要父 method 是可调用的,你可以在覆写子 method 之前存储它,比如说

function Label(_x, _y, _text) : Element(_x, _y) constructor {
    static __step = step; // 现在引用父 constructor 的 step 函数
    static step = function(_ofs_x, _ofs_y) {
        __step(); // 调用父 constructor 的 step 函数
        // ...
    };
    // ...
}

异常处理

GameMaker 函数的结构通常是不抛出错误的,除非它肯定是你的错——所以,例如,试图打开一个不存在的文本文件将返回一个特殊的索引 -1,但试图从一个无效的索引读取将抛出一个错误。

不过,写允许失败的代码还是很方便的,不需要在过程的每一步插入安全检查。现在你可以了! 其工作原理如下。

try {
    // (可能引发错误的代码)
    var a = 1, b = 0;
    a = a div b; // 导致 "除以零 "的错误
    show_debug_message("this line will not execute");
} catch (an_exception) {
    // 对错误信息做一些事情(或不做),这些信息是
    // 现在存储在局部变量 an_exception 中。
    show_debug_message(an_exception);
}

"内置 "错误是带有几个变量的结构。

  • message: 一个字符串,包含对错误的简短描述。例如,如果你试图做整数除以 0,它将是 "DoRem :: Divide by zero"。
  • longMessage: 一个对错误和 callstack 有较长描述的字符串。如果你不处理这个错误,这将出现在内置的错误弹出窗口。
  • Stacktrace: 表示调用堆栈的字符串数组 - 导致问题点的一连串函数名。当从 IDE 或使用 YYC 运行时,行号将包含在每个函数名之后(例如 gml_Script_scr_hello(第 5 行))。
  • script: (技术上的)错误起源的脚本/函数的名称。这与抓取 stacktrace 中的第一项没有太大区别。

你也可以抛出你自己的异常--可以通过调用 show_error 和错误文本。

try {
    show_error("hey", false);
} catch (e) {
    show_debug_message(e.message); // "hey"
}

或通过使用 throw 关键字(允许任意的值被 "抛出")。

try {
    throw {
        message: "hey",
        longMessage: "no long messages today",
        stacktrace: debug_get_callstack()
    }
} catch (e) {
    show_debug_message(e); // 输出上述 struct
}

Try-catch 块可以嵌套在同一个或不同的脚本中。

当这种情况发生时,最近的捕获块将被触发。

如果你不想处理一个异常,你可以“重新抛出”它。

try {
    try {
        return 10 / a_missing_variable;
    } catch (e) {
        if (string_pos("DoRem", e.message) != 0) {
            show_debug_message("Caught `" + e.message + "` in inner catch!");
        } else {
            throw e;
        }
    }
} catch (e) {
    show_debug_message("Caught `" + e.message + "` in outer catch!");
}

如果一个异常没有被捕获,你会得到熟悉的错误弹出窗口。除非……

exception_unhandled_handler

在可以被认为是最后一道防线的情况下,GMS2 现在还提供了一个函数,当一个异常没有被捕获,你的游戏即将关闭时,这个功能将被调用。这覆盖了默认的错误弹出窗口。

exception_unhandled_handler(function(e) {
    show_message("Trouble!\n" + string(e.longMessage));
});
show_error("hey", true);

正如文档所指出的,在这一点上你能做的不多,但你可以将错误文本(连同任何可能证明有用的上下文)保存到一个文件中,这样你就可以在游戏开始时加载它,并为用户提供一个报告。

较小的添加物

主要是便利功能。


String functions

增加了 string_pos_extstring_last_posstring_last_pos_ext,以处理从偏移量和/或从字符串末尾开始搜索子串的问题,这对解析数据很有帮助--例如,见我以前的“在分隔符上分割字符串”的帖子。


Array functions

增加了一些数组函数来处理数组。

array_resize(array, newsize) 这将一个数组的大小调整为新的大小,要么在数组的末尾添加零,要么删除元素以满足大小。

var arr = [1, 2, 3];
array_resize(arr, 5);
show_debug_message(arr); // [1, 2, 3, 0, 0]
array_resize(arr, 2);
show_debug_message(arr); // [1, 2]

使得其他各种实用函数得以实现。


array_push(array, ...values) 将一个或多个值添加到一个数组的末端。

var arr = [1, 2, 3];
array_push(arr, 4);
show_debug_message(arr); // [1, 2, 3, 4]
array_push(arr, 5, 6);
show_debug_message(arr); // [1, 2, 3, 4, 5, 6]
array_insert(array, index, ...values)

array_insert(array, index, ...values) 在一个数组中的偏移处插入一个或多个值。

var arr = [1, 2, 3];
array_insert(arr, 1, "hi!");
show_debug_message(arr); // [1, "hi!", 2, 3]

array_pop(array)➜value 移除数组中的最后一个元素,并将其返回。

var arr = [1, 2, 3];
show_debug_message(array_pop(arr)); // 3
show_debug_message(arr); // [1, 2]
array_delete(array, index, count)

array_delete(array, index, count) 删除数组中某一偏移处的元素

var arr = [1, 2, 3, 4];
array_delete(arr, 1, 2);
show_debug_message(arr); // [1, 4]
array_sort(array, sorttype_or_function)

array_sort(array, sorttype_or_function) 对一个数组进行升序/降序排序(就像 ds_list_sort 一样)。

var arr = [5, 3, 1, 2, 4];
array_sort(arr, true);
show_debug_message(arr); // [1, 2, 3, 4, 5]

或通过提供的“comparator”函数传递每个元素

var strings = ["plenty", "1", "three", "two"];
array_sort(strings, function(a, b) {
    return string_length(a) - string_length(b);
});
show_debug_message(strings); // [ "1","two","three","plenty" ]

script_execute_ext

记得我们以前通过 switch 语句来根据某种情况 script_execute 么?现在不需要了。

var arr = [1, 2, 3, 4];
var test = function() {
    var r = "";
    for (var i = 0; i < argument_count; i++) {
        if (i > 0) r += ", ";
        r += string(argument[i]);
    }
    show_debug_message(r);
}
script_execute_ext(test, arr); // `1, 2, 3, 4` - 整个 array
script_execute_ext(test, arr, 1); // `2, 3, 4` - 从偏移量开始
script_execute_ext(test, arr, 1, 2); // `2, 3` - 偏移量和计数

数据结构检查

增加了四个函数,用于检查 ds_listds_map 项是否为 map/list:

ds_list_is_map(id, index)
ds_list_is_list(id, index)
ds_map_is_map(id, key)
ds_map_is_list(id, key)

这可以验证你正在访问的东西(特别是对于 json_decode 输出)确实是一个 map/list


ds_map functions

增加了两个函数用于枚举 map 的键/值:

ds_map_values_to_array(id,?array)
ds_map_keys_to_array(id,?array)

这些对于迭代大型 map 来说是很方便的,特别是如果你希望在迭代过程中修改它们(这就是 ds_map_find_* 函数有未定义行为的地方)。


类型检查功能

is_struct, is_method 已经被加入,用于检查一个值是否是一个结构或一个绑定的函数,但还有一个额外的功能--is_numeric 将检查一个值是否是任何数字类型(real, int32, int64, bool)。

突破性改变

需要注意的几件事:


2d array functions

由于 2d 数组函数现在已被废弃,它们翻译成如下。

  • array_length_1d(arr) ➜ array_length(arr)
  • array_height_2d(arr) ➜ array_length(arr)
  • array_length_2d(arr, ind) ➜ array_length(arr[ind])

这里的意思是 array_height_2d 并不关心你的数组是否真的是 2D 的(里面有子数组),因此在 1D 数组上使用时会返回意外的值--例如 array_height_2d([1, 2, 3]) 是 3。

你可以通过以下方式来解决这个问题

function array_height_2d_fixed(arr) {
    var n = array_length(arr);
    if (n == 0) return 0; // 空/不是一个数组
    for (var i = 0; i < n; i++) if (is_array(arr[i])) return n;
    return 1; // 里面没有数组
}

(只有当数组包含子数组时才会返回 > 1)

但是这仍然会对包含 1d 数组的 1d 数组产生误报,因为现在 2d 数组就是这样。


默认返回值

以前,如果脚本/函数没有返回任何东西,则脚本/函数调用会返回 0

现在它们会返回 undefined

这通常是一个很好的变化,因为 GameMaker 在很多地方仍然使用数字 ID(忘记返回一个值可能会导致你使用一个有效但不相关的结构,索引为 0),但可能会打破旧的代码,这些代码只能通过偶然的机会真正起作用。

在 2.3.1 中,一些内置函数也同样被修改为如果它们不应该返回任何东西,则返回 undefined(以前也是 0)。


self/other 值

在 GameMaker≤8.1 时代,写

show_debug_message(self);
show_debug_message(other);

将分别显示 -1-2,这在大多数函数中被视为一种特殊情况。

这在 GMS1 中被改变了,相当于 self.idother.id

现在这一点又被改变了,self/other 现在给你提供了实例“structs”--所以

hi = "hello!";
show_debug_message(self);

现在将显示 { hi : "hello!" }。这有一些影响。

  • self-struct 不等于 self.id,所以依赖它的旧代码会被破坏。(在这种情况下,对 self 的使用最好用 self.id 代替)。
  • 与通过 ID 引用不同,使用实例结构,即使实例已经通过 instance_destroy 从房间中移除,你也可以使用实例变量(但仍然可以使用 instance_exists 检查它是否在房间中)。


Prefix-ops as then-branch

这种

if (condition) ++variable;

这种

if (condition) --variable;

由于各种新的句法结构造成的歧义,不再允许使用,这使得很难判断您的意思是 if (condition)++  (条件表达式的后增量) 还是 if (condition) ++ (在 then-branch 表达式上预增量)。

如果您想要个人看法,我宁愿禁止将 (variable)++ 等同于 variable++ - 我认为我没有看到在任何项目中有意使用这种构造。

无论如何,这很容易解决。


array[$hex]

由于 a[$b] 现在用于结构访问器(见上文),试图做 array[$A1](以前用 Pascal 风格的十六进制字头索引的数组访问)将不会像以前那样工作(而是试图从一个叫 A1 的变量中读取键)。

你会想把它改为 array[ $A1](为了清晰起见,有一个空格)或 array[0xA1](C 语言风格的十六进制字面)。


image_index

以前,image_index 被允许溢出 image_number,这将使它在绘图时循环(image_index % image_number)。

在 2.3 版本中,试图分配 image_index 超过 image_number 时,会在分配时将其循环回来,这意味着:

sprite_index = spr_3_frames;
image_index = 4;
show_debug_message(image_index);

将显示 1 而不是 4

在大多数情况下,这是无害的,并修复了一些与在游戏启动时保存越来越大的索引有关的奇怪现象,但这确实意味着,像

if (image_index >= image_number) {
    image_index = 0;
    sprite_index = spr_other_sprite;
}

将不再触发,需要进行修改。


buffer_get/set_surface

当导入旧项目到 2.3.1 时,你会经常看到以下错误。

wrong number of arguments for function buffer_get_surface
wrong number of arguments for function buffer_set_surface

这是因为在 2.3.1 之前,这些函数有如下签名。

buffer_get_surface(buffer, surface, mode, offset, modulo)
buffer_set_surface(buffer, surface, mode, offset, modulo)

而现在他们有了以下内容。

buffer_get_surface(buffer, surface, offset)
buffer_set_surface(buffer, surface, offset)

有关这方面的更多信息,请见此文

结论和进一步阅读

请放心,2.3 的变化是非常令人兴奋的,并且拓宽了在 GML 中可以做的事情的视野。最值得注意的是,许多 JavaScript 代码现在可以很容易地被移植到 GML 中,正如用户创建的库(如 GMLodash)所展示的那样。


关于这里可能没有涵盖的细节,你可以查看

玩得开心!


2021 年 12 月 3 日

近期点赞的会员

 分享这篇文章

您可能还会对这些文章感兴趣

参与此文章的讨论

  1. 流贾君 2021-12-10

    依旧实用,马克后食用。

  2. 顺子 2021-12-12

    最后的2.3资源doc实在是太详细了!

您需要登录或者注册后才能发表评论

登录/注册