GameMaker Studio 2

创建于:2017-04-19

创建人: dougen

192 信息 1091 成员
游戏开发工具 GameMaker Studio 2 的讨论小组

译:Gamemaker Studio 2.3 语法详解

highway★ 2021-12-03

作者: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)

GMS2.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中的每一个 "实例"--我们需要将它们添加到一个array或list中。

-----------------------------------------------------------------------------------------------------------------------------------

Structs as maps

与实例类似,struct有variable_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_stringify和json_parse函数,它们与现有的json_encode和json_decode很相似,但使用的是struct和array,而不是像之前的和map和list。

我们可以这样:

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 <name>() {<code>} 语法来区分:

/// @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之前版本的兼容性。
  • 绑定到self的function 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 variable和new struct的变量:

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

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

-----------------------------------------------------------------------------------------------------------------------------------

Inheritance(继承)

一个constructor可以使用 : Parent(<arguments>) 语法从另一个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_ext、string_last_pos和string_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_list和ds_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.id和other.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)++ <expr> (条件表达式的后增量) 还是 if (condition) ++<expr> (在 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)所展示的那样。


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

官方博文 

官方指南 

在线2.3手册 

各种2.3资源的链接 

玩得开心!



2021年12月3日

(转发自:原日志地址

近期喜欢的会员

 
yellow 2021-12-04

看文档非常重要。

 
NickyKendy Hua 2021-12-28

整理的好多好全面!唉。。。已经转去Godot两三年了,发现GMS2更新了好多,但已经丢的差不多了

 

加入 indienova

  • 建立个人/工作室档案
  • 建立开发中的游戏档案
  • 关注个人/工作室动态
  • 寻找合作伙伴共同开发
  • 寻求线上发行
  • 更多服务……
登录/注册