译:Gamemaker Studio 2.3 语法详解
译: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)所展示的那样。
关于这里可能没有涵盖的细节,你可以查看
玩得开心!
2021年12月3日
晴
整理的好多好全面!唉。。。已经转去Godot两三年了,发现GMS2更新了好多,但已经丢的差不多了
看文档非常重要。