GameMaker Studio 2

创建于:2017-04-19

创建人: dougen

186 信息 998 成员
游戏开发工具 GameMaker Studio 2 的讨论小组
GameMaker Studio 2小组导航 :介绍、愿景、内容
Linpean 2017-10-27

我最近半年开始学习GameMaker Studio 2(以下简称GMS2),向往着变成一名独立开发者,同时也在了解GMS2的引擎操作,阅读F1帮助文档,趴教程。可以说 GMS2 的中文用户很少,百度贴吧关注人数也未破万,资料很不成体系,很多前人的资料和文章都未被人整理,压在不知名的角落,我希望能够帮助其他的中文用户了解GMS2,所以建立一个快速导航,帮助大家快速开始GameMaker Studio 2的学习。

愿景和管理工作

希望大家不要在公开的帖子中留下联系方法或询问他人的联系方式,而是将有关一个话题的讨论都公开在帖子中,如果真的是要私下联系,可以使用私信的功能。我不希望看到的是一个人提出一个问题后通过私下的沟通解决了问题,而关于解决问题的过程并没有留在帖子中,无法给搜索到这个帖子的人提供帮助。

  • 希望大家不要重复发帖,将与同一话题的讨论集中到一个帖子中
  • 希望大家在发表帖子和回复之前先考虑一下措辞,事后发现错误也及时订正
  • 希望大家不要发表无价值的回复,当你只是觉得某一个帖子好的时候,请使用「点赞」功能

管理员的工作主要是维护秩序,维护的主要目标是方便他人检索和阅读信息,希望大家不要感到不满。

  • 修改帖子标题以符合帖子内容,通常会修改几乎所有帖子标题
  • 修改正文和回复中明显的错别字、将代码编辑代码块
  • 将重复的帖子导向讨论更多的帖子,并移除新的重复的帖子
  • 删除重复或无意义的回复,删除不当内容

导航模块

  • 快速索引社区:国内比较大的GameMaker中文社区和推荐的QQ群
  • 求助:提出你在使用过程中遇到的问题,寻找需要的功能和插件,管理员会不定期整理QA内容,防止日经QA
  • 开发:讨论与游戏开发有关的话题,或发布你自己开发的游戏
  • 分享:原创文章、资料汇总、翻译自官网的文章和文档

快速索引社区

  1. Yoyogames官网
  2. GMS2官方文档
  3. GMS2文档红色激情汉化版
  4. GameMaker开发者之家(维护中)
  5. GameMaker百度贴吧
  6. GameMakerStudio2 Wiki(正在创建中)
  7. GameMaker开发者之家QQ群:235271204
  8. GameMaker:Studio2 菜鸟群:102797189
  9. GameMaker:Studio2入门小站

求助整合

  1. 官网如何购买,如何注册?
  2. 国内更新太慢,怎么办?


开发

    待续

[ 分享:入门 ] Make an RPG:开始我们的RPG之旅
Linpean 2017-10-23

文档说明

本系列是油管上的HeartBeast[Beginner] Make an RPG课程的中文笔记,主要形式是截图的方式进行步骤上的说明。

由于原教程是基于GMS1版本的,我这里是用GMS2版本进行制作的,界面和部分函数都有变化,有错漏的地方,请参考原视频和官方帮助文档。接触GameMaker时,苦于国内没有完整的一个RPG教程,诺娃上的青铜的幻想GameMaker: Studio 中文教程可惜因为作者的工作,没有持续更新下去。而红激的教程也是以FC上的小游戏为切入点,只好上油管,HeartBeast的教程很丰富,有平台跳跃、射击、也有角色扮演。

面向对象:GameMaker新手,以学习一门脚本编程语言,制作一个RPG游戏为目标的爱好者。

主要包括以下内容(蓝色标识已完成,目前进度 :18/30):

相关参考资料:

  1. GMS官方说明文档
  2. [Beginner] Make an RPG--HeartBeast
  3. GameMaker: Studio 中文教程--青铜的幻想
  4. GMS2官方中文教程系列--顺子
游戏出海本地化,助力各类游戏扬帆起航!!!
liangdarren 2022-04-13

我们公司成立于 2000 年,主要从事笔译、口译、游戏本地化,短视频翻译等语言服务和各类 IT 服务的跨国企业,目前业内排名全球前五十;全球合作译员大概有30000名,可以翻译全球230种语言合作的知名企业包括:网飞,迪士尼,拳头游戏,美国艺电,腾讯,网易等等,有需要的小伙伴欢迎私聊进群畅聊出海,研发,发行问题或咨询合作,扣扣:619776218;VX:15301140998;

译: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日

(转发自:原日志地址
SNOWFALL DEVLOG_03
highway★ 2021-11-22

经历了大概10天的严重睡眠不足,白天大部分时间不是补觉就是打瞌睡。接着生病,不过好在一天就恢复了。
基本上这些天都在做3C,发小给的建议是花大量时间在3C上再往后推,要不然可能会有很多返工,可能还要持续一段时间。

Image title

之前的准星和移动处理,基本上和nuclear throne差不多,开始测试的时候我用的手柄(像helldivers那样,没有开启准星),忽略了键鼠操作准星可能导致的问题。在键鼠下,当鼠标(准星)固定在某一个点时,角色会在移动的时候根据瞄准角度(鼠标位置)去自动转身,会有点奇怪,就像车在漂移,这个体验很难受。就改成了helldivers那种逻辑,在非瞄准状态下,射击方向和朝向匹配,这样在键盘操作上,如果不瞄准,就是8方向射击,更复古,也可以在不减少移动速度的情况下向一个固定方向射击;如果在比较安全的距离,利用瞄准来精确射击。

Image title

精确射击本来打算给敌人设计弱点区,比如这样

Image title


但是top-down 3/4 这个视角很尴尬,比如横版卷轴或者3D TPS、FPS来说,敌人和玩家是站在地上,但在2d top-down  这个视角下,其实敌人和玩家实际上是躺在地上 =_=

后来想如果做多个hitbox,在子弹上再加raycast处理来检测hitbox,后来想到如果关联到动画上可能工作量挺大,有点嫌麻烦,暂时先用瞄准线和敌人的中心点之间的角度来处理精确瞄准。后面还是需要试一下raycast,哪怕只给boss或者比较强的敌人做这个功能,这个如果实现了,战斗会更有意思一些,对于枪法好的人来说。

Image title

另外学着用动画曲线做了一个完美换弹的试验,不过可能并不用像战争机器那样麻烦,可能Returnal那种处理更简化一些(虽然是建立在无限子弹的基础上),还没有考虑好,暂时没有扔进工程。

Image title

………………………………………………………………………………………………………………………………………………………………………………………………………………………………

在写代码和想功能之外,做美术资源的时候开始比较头疼,由于想做非像素,就没办法使用aseprite,用photoshop在做动画上折磨了大概一天,实在是受不了了,找了一些做动画的插件,看视频感觉也不是很方便,就用去年疫情时候买的优动漫的正版开始做角色粗略动画,但是做到一半……发现动画的帧数还有限制,wtf,只好去买了个破解版的Clip Studio Paint EX(功能全部开放),正版我实在是承受不起,在导出的时候CSP只能导出单帧图像,并不像aseprite那么方便,还要用Free texture packer处理成条状图再回photoshop里加颜色,不过整个流程都比只在PS里做动画心情舒畅多了。(顺便说一下,这个软件千万不要像我这样装繁体中文版,很多地方都看不懂,靠猜……还不如在安装的时候直接选择英文了)

Image title

Image title

………………………………………………………………………………………………………………………………………………………………………………………………………………………………

大前天在sprite editor里操作的时候,GMS2直接闪退(没有报错弹窗),重启后并没有任何提示信息,我也没当回事就忘了这茬儿。

前天晚上孩子睡觉以后,突然想去改一个功能,然后就出现了资源树里object索引出错的问题,无法新建任何object,只要新建,各种代码报错(注释掉相关的,其他报错,无限),只要删除新建的object,恢复,google搜了很多也没找到相关的情况。

在请教了@流贾君 之后,只能导出所有文件做yymp重开项目,成功救回来了工程。

昨晚在@流贾君的过程中还发现音频组合材质组不能改名了(搜索了一下,在reddit和gms社区也有人发帖说这个问题)………上个月新建工程的时候还可以,我换了上一次的runtime发现也不能改名,暂时只能等更新了,不敢用beta版。

………………………………………………………………………………………………………………………………………………………………………………………………………………………………

有用的连接:

GMS2 BLOG上的文章,不单单可以扩展世界,利用这个方法可以像魂系列那样来进行敌人的刷新处理

EXPANDING WORLDS: BUILDING GAMES WITH INTERCONNECTED LEVELS

由于更新2.3之后都没咋用过,看了这个教程感觉要补习很多东西,新加的constructor和static,要花时间仔细学学用法。

Static Variables

Struct Inheritance

(上面俩视频连接都来自Youtube的SamSpadeGameDev,这个频道很不错)

………………………………………………………………………………………………………………………………………………………………………………………………………………………………




2021年11月22日周一 

早上阴沉沉 这会儿大太阳

Highway


(转发自:原日志地址
游戏音乐音效配音
jyy1108 2021-11-08

,10余年专注于游戏音乐外包,使小旭团队清楚的了解玩家对音乐的喜好和需求。 各位着急不着急的都来看看,企业qq:3001547718

T.M.D 最担心的事儿果然发生了
notlsd 2021-08-13

https://www.youtube.com/watch?v=ibI7gUVPmgo

Gamemaker Studio 2的奇怪小技巧001 - 快速提取Room内instance的各项资料
流贾君 2021-07-25

        因为某个游戏项目的需要,需要提取某个特定Room内instance的各项信息,坐标xy,depth之类的。用于在另一个房间通过不同条件控制并重现这些instasnce,数量少还好,但是数量大的时候一个个手动查看录入实在太不科学。在友人@顺子的提示下,于是有了下面这一段代码。

1、创建一个object,命名为obj_temp_datacollect。

2、在Step里输入下面这段代码,也可自行在with里面添加其他内容,image_alpha,image_blend之类的。

if keyboard_check_pressed(ord("P"))
{
    ini_open("temp_mark.ini");
    for(var i = 0; i < instance_count; i ++;)
    {
        with(instance_id[i]) 
        {
            var oobject_name = object_get_name(object_index);
            var xx = string("xx=" + string(x));
            var yy = string("yy=" + string(y));
            var ddepth = string("ddepth=" + string(depth));

            //分段
            /*
            ini_write_string("data", oobject_name+".name"+string(i), object_get_name(object_index))
            ini_write_string("data", oobject_name+".x", x)
            ini_write_string("data", oobject_name+".y", y)
            ini_write_string("data", oobject_name+".depth", depth)
            */

            //一行
            ini_write_string("data", oobject_name + "." + string(i), oobject_name + "," + xx + "," + yy + "," + ddepth);
        }
    }
    ini_close();
}

3、运行游戏工程,至目标room,按一下P,关闭工程。

4、打开资源管理器,C盘-用户-用户名-AppData-Local-项目名,找到temp_datacollect.ini,打开并提取所需内容。


        完。如果有更好更快更巧妙的方法,欢迎大家交流讨论,虚心学习一下。

(转发自:原日志地址
GameMaker Studio + Opera GX 浏览器 - Amaze Me Game Jam 活动 火热报名中!
indienova 2021-06-08

GameMaker Studio 与 Opera GX 浏览器合办了 Amaze Me Game Jam 活动 火热报名中!


本次主题是 NEON(霓虹)

可以自由发挥,在游戏中使用霓虹灯元素,或打造一个满是霓虹灯光的赛博朋克范儿都市都行,尽情发挥!


基本规则

  • 游戏必须使用 GameMaker Studio 2 制作。
  • 游戏必须紧扣主题。
  • 游戏必须提交项目文件以进行验证。
  • 游戏不能包含不适合未成年人的主题,例如吸毒、性主题、野蛮暴力、令人不安的视觉效果等。
  • 允许组队。

奖金

  • 一等奖: $1500 USD
  • 二等奖: $1000 USD
  • 三等奖: $500 USD

点击前往 itch 报名参加!

https://itch.io/jam/amaze-me-game-jam

GMS 软件中国区促销活动

为了支持中国用户参与此次活动,GameMaker Studio 特此开通了官方淘宝直营店,方便大家提供更加顺畅的购买渠道;

GMS Desktop、Mobile、Web 版本各释放出 50 个名额,248 元基础上返现 50 元,具体返现方式可咨询官方淘宝店客服!

中文语言问题请求帮助
T.L 2021-05-28

今天刚下载了2.32版本。在设置面板选择了中文语言。面板全变行书了。有大佬知道怎么补救么?电脑是苹果笔记本MAC系统。

Image title

游戏本地化翻译公司,欢迎各位出海的朋友来撩~
大海 2021-05-11

专注游戏本地化翻译,配音,随时欢迎各位老板来试译,希望能长期合作,vx:zzh564183167

寻找美术小伙伴
wx09y 2021-04-25

本人正在独立开发一款自走棋类游戏,棋子全部使用网络素材,需要找一个美术小伙伴设计ui界面

一次性付费或者发售后奖励都行

有意者请联系qq949553079,谢谢

战旗冒险,开发日志01
wx09y 2021-04-11

战旗冒险终于完成了初期版本,战斗,房间选择,棋子特殊能力,各种抉择事件等等,能够正常的通关了

现在最大的问题,就是没有美术小伙伴,自己瞎画,看起来非常的闹着玩

Image title


游戏主要灵感来自酒馆战旗,战斗也是棋子依次对撞的形式

每回合都会得到金币,经验值,用来提升自己阵容的战斗力

对战双方都有前排和后排,前排单位先攻击,也会优先被敌人攻击.有些棋子可以直接攻击后排

选择不同房间会有不同奖励,有金币,经验,或者直接强化属性Image title


下发的平原2/5,指的是某个地名进入5次会获得更多奖励.

如果大家有更多好点子,也欢迎多提提意见啊

b站视频



(转发自:原日志地址
艹,YoYo Game 被 Opera 收购了,背后可是 360
notlsd 2021-01-21

虽然没有什么直接的鲜见后果,但整个人都不好了

游戏项目演示:《隔离》 (游戏设计文档)
cfan_yjr 2020-11-07

故事背景

玛丽亚他们一行考察队在一个外星球上。当她正在研究一些古迹时,出现了意外,隧道崩塌了,她和她的团队分开了。 现在她一个人,孤立无援,只能努力找到另外一条回去的路。

游戏玩法

游戏主要围绕探索和作战行动展开。利用沿途发现的物品,玩家将能够升级玛丽亚的装备,对抗不断变强的敌人。

胜利条件

正如其他的类银河城游戏一样,当到达关底或者杀死BOSS后,就获胜。

当然,我们将要开发的游戏版本是初级版本,流程非常短。我们只会专注于游戏系统的创建。所以这次我们不会添加胜利的条件。这个游戏只是一个原型,用于演示类银河城游戏的主要功能。

操作方式

有两种,一是键盘控制,二是手柄控制。

键盘控制方式如下

左右方向键:左右移动

Z:跳跃

X:冲刺

C:攻击

Esc: 暂停游戏

I:打开道具栏

Tab:打开地图

Enter:确定

手柄控制方式如下

左右方向键:左右移动

A:跳跃,确定

RB:冲刺

X:攻击

Start:暂停游戏

Y:打开道具栏

Select:打开地图

敌人

用之前做的游戏里的现成素材,绿色章鱼怪,左右来回移动,在自己的巡逻路径里移动,不会主动攻击。

攻击方式

玛丽亚有一把能量手枪,是太空考古学家的标准装备。

这把特殊的手枪可以用某些材料升级,她会在沿途发现改变武器特性的材料。

玛丽亚可以获取和装备物品。


技能

玛丽亚有两大技能:

冲刺:快速向前迈出

墙跳:跳到墙上,让她到达更高的地方。

地图

《隔离》这款游戏有地图系统,将能帮助玩家实时的跟踪他们的行走轨迹。

地图系统是由一个个小矩形组合而成。通过按Tab键或者Select键来打开。

道具

《隔离》这款游戏还有一个特点是有道具栏,它可以存储玛丽亚沿路获得的物品,同时,玛丽亚也可以装备这些物品。

我们将更多地集中在技术实现的层面上,所以美学方面将不是重点。 存储道具的界面将会很简单,只是一个项目列表,随时能够更新玛丽亚放入的物品信息。

(转发自:原日志地址
模板卷轴游戏--樱桃2
cfan_yjr 2020-09-22

故事背景

Berry带着樱桃从洞穴出来,把樱桃分发给人们。他被人们当作英雄。可是,只有他自己知道,更大的危机就要来临。因为那些绿色球是外星人的幼体。它们已经被孵化出来了,马上要重回地面了。

只有一个人能利用樱桃的力量来打败外星人:Berry!


游戏玩法

目标是到达关底,不像第一部是收集齐樱桃。全新的敌人也会登场,会有不同的行为模式。

胜利条件

完成每一关的条件是到达关底,如果玩家受到攻击,就会丢一条命。并重启关卡。只有三条命。

游戏道具

樱桃收集后,可以提高玩家的力量,让玩家有一段无敌时间。新增金币道具,用于收集后,提高玩家的分数。

玩家的控制

键盘控制。方向键,用于左右行走,上下爬梯。空格键是跳跃,ESC打开菜单,回车键为确定。

全新的敌人

绿色八爪鱼,紫色八爪鱼。

Image title

Image title

对敌人的攻击方式

这次主角不像第一部,没有手段来攻击敌人。除了吃樱桃让自己无敌外,现在还可以踩死敌人。

其他杂项

会掉落的平台,当主角接触到它,就会往下掉。

Image title


移动的平台,会来回移动。主角可以用它去一些特殊的地方。

Image title


跳跃平台,当主角在上面跳跃时,会跳得更高,从而来到更高的地方。

Image title

这些是第二部游戏主要的素材了。

(转发自:原日志地址
学习制作游戏--单屏平台游戏--游戏文档制作 1
cfan_yjr 2020-08-30

    游戏名字叫《樱桃》,在迷宫里收集樱桃。

故事背景

因为外星人的占领,樱桃,成为了一个难以获得的食物。为了能够获得樱桃,人们愿意出大价钱来购买。你听说有一个洞穴里长着许多的樱桃,为了发财,你勇敢地出发了。冒险就此开始。

游戏目标

把每一关的樱桃全部收集完,并成功撤离。在这期间,因为敌人而存在一定的危险。玩家被敌人击中三次就会失败。每次击中,玩家都会回到这关的起点。

游戏控制

方向键左右移动,上下爬梯子。空格键跳跃。ESC键,菜单功能。

敌人

他们会按照设计好的路线行进,不会主动攻击玩家。有两类,紫色和绿色。

紫色是跳跃的行动模式,来阻挡玩家。绿色则是带有一定的智力,来阻挡玩家。


游戏素材

Image title


spr_player_idle

50*64

Image title

spr_player_walk

50*64

Image title

spr_player_climb

50*64

Image title

spr_block_red

64*64

Image title

spr_block_brown

64*64

Image title

spr_ladder

64*64


Image title

spr_ball_purple

64*64

Image title

spr_ball_green

64*64

Image title

spr_cherry

64*64

Image title

spr_goal

64*64

以上素材的原点都设置在正中心。

字体,根据自己的喜好,选择一个。名称为fnt_score.

音效有5种,分别是菜单音效,目标达成音效,收集樱桃的音效,玩家跳音效和受到敌人伤害时的音效。

(转发自:原日志地址
IDE更新的问题

更新了IDE后,旧试用版IDE创建的yyp文件打不开怎么办?

太空小蜜蜂01--素材的准备
cfan_yjr 2020-08-01

游戏的故事背景

你是一个太空战士,是人类最后的希望。你的任务是保卫太空站,摧毁外星人的战舰。

游戏玩法

玩家是边射击边躲避的操作。关卡难度,逐渐递增。

胜利条件

每一关,消灭所有的敌人。如果敌人到达太空站,则游戏失败。玩家没有生命,也是游戏失败。

控制方式

玩家用方向键,进行左右移动,按空格键,攻击。ESC键,弹出菜单。

菜单

在菜单里可以,关闭游戏,重开游戏,返回游戏。


敌人

只有一种类型的敌人。左右移动,随机射击。生命值为1,攻击值为1.


打开Gamemaker Studio,新建一个工程命为Space Gala,导入游戏素材。

spr_player


50*43的大小,参考点设置在中心。碰撞图形为默认的矩形。名称为spr_player

spr_bullet_player


16*16的大小,参考点在中心。碰撞图形为矩形。名称为spr_bullet_player

spr_life

16*16的大小,参考点在中心。名称为spr_life

spr_enemy_red


16*18的大小,参考点在中心。碰撞图形为矩形。名称为spr_enemy_red

spr_background

256*256的大小。这是游戏背景。名称为spr_background.

还要建立两种字体文件。fnt_score,大小为14,字体为Arial,fnt_message,大小为14,加粗。

把原来房间重命名为rm_level_1,把游戏背景添加上去。如下图所示

游戏背景添加

这样,太空小蜜蜂的游戏元素就差不多都准备好了。

(转发自:原日志地址

加入 indienova

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