GameMaker Studio 2小组导航 :介绍、愿景、内容
我最近半年开始学习GameMaker Studio 2(以下简称GMS2),向往着变成一名独立开发者,同时也在了解GMS2的引擎操作,阅读F1帮助文档,趴教程。可以说 GMS2 的中文用户很少,百度贴吧关注人数也未破万,资料很不成体系,很多前人的资料和文章都未被人整理,压在不知名的角落,我希望能够帮助其他的中文用户了解GMS2,所以建立一个快速导航,帮助大家快速开始GameMaker Studio 2的学习。
愿景和管理工作
希望大家不要在公开的帖子中留下联系方法或询问他人的联系方式,而是将有关一个话题的讨论都公开在帖子中,如果真的是要私下联系,可以使用私信的功能。我不希望看到的是一个人提出一个问题后通过私下的沟通解决了问题,而关于解决问题的过程并没有留在帖子中,无法给搜索到这个帖子的人提供帮助。
- 希望大家不要重复发帖,将与同一话题的讨论集中到一个帖子中
- 希望大家在发表帖子和回复之前先考虑一下措辞,事后发现错误也及时订正
- 希望大家不要发表无价值的回复,当你只是觉得某一个帖子好的时候,请使用「点赞」功能
管理员的工作主要是维护秩序,维护的主要目标是方便他人检索和阅读信息,希望大家不要感到不满。
- 修改帖子标题以符合帖子内容,通常会修改几乎所有帖子标题
- 修改正文和回复中明显的错别字、将代码编辑代码块
- 将重复的帖子导向讨论更多的帖子,并移除新的重复的帖子
- 删除重复或无意义的回复,删除不当内容
导航模块
- 快速索引社区:国内比较大的GameMaker中文社区和推荐的QQ群
- 求助:提出你在使用过程中遇到的问题,寻找需要的功能和插件,管理员会不定期整理QA内容,防止日经QA
- 开发:讨论与游戏开发有关的话题,或发布你自己开发的游戏
- 分享:原创文章、资料汇总、翻译自官网的文章和文档
快速索引社区
- Yoyogames官网
- GMS2官方文档
- GMS2文档红色激情汉化版
- GameMaker开发者之家(维护中)
- GameMaker百度贴吧
- GameMakerStudio2 Wiki(正在创建中)
- GameMaker开发者之家QQ群:235271204
- GameMaker:Studio2 菜鸟群:102797189
- GameMaker:Studio2入门小站
求助整合
- 官网如何购买,如何注册?
- 国内更新太慢,怎么办?
入门教程
开发
待续
[ 分享:入门 ] Make an RPG:开始我们的RPG之旅
文档说明
本系列是油管上的HeartBeast的[Beginner] Make an RPG课程的中文笔记,主要形式是截图的方式进行步骤上的说明。
由于原教程是基于GMS1版本的,我这里是用GMS2版本进行制作的,界面和部分函数都有变化,有错漏的地方,请参考原视频和官方帮助文档。接触GameMaker时,苦于国内没有完整的一个RPG教程,诺娃上的青铜的幻想的GameMaker: Studio 中文教程可惜因为作者的工作,没有持续更新下去。而红激的教程也是以FC上的小游戏为切入点,只好上油管,HeartBeast的教程很丰富,有平台跳跃、射击、也有角色扮演。
面向对象:GameMaker新手,以学习一门脚本编程语言,制作一个RPG游戏为目标的爱好者。
主要包括以下内容(蓝色标识已完成,目前进度 :18/30):
- 1.BASIC MOVEMENT AND COLLISIONS(基础移动和碰撞)
- 2.STEP EVENT + VIEWS(步事件和视图)
- 3.VARIABLES AND CHARACTER SPRITES(变量和人物精灵)
- 4.DRAW EVENT, BACKGROUNDS, SCRIPTS, AND STATES(绘制事件,背景,脚本,状态)
- 5.SMOOTH VIEW MOVEMENT(平滑的移动视图)
- 6.BETTER PLAYER MOVEMENT(优化角色移动)
- 7.GAMEPAD INPUT(手柄输入)
- 8.DASH STATE(冲锋状态)
- 9.ADDING ENEMIES, DEPTH, AND INHERITANCE(增加敌人,理解深度和继承)
- 10.PLAYER ATTACK STATE(攻击状态)
- 11.HITBOXES AND KNOCKBACK(受击盒和击退)
- 12.BETTER CHARACTER SPRITE CONTROL(角色精灵控制优化)
- 13.ADDING ENEMY STATES(怪物状态机)
- 14.ENEMIES THAT ATTACK(怪物攻击)
- 15.ADDING STATS FOR THE PLAYER(人物状态)
- 16.LEVEL UP!(升级)
- 17.LIMIT DASH AND FIX A BUG(冲锋限制、修复BUG)
- 18.FIXED ENEMY MOVEMENT(修复怪物移动)
- 19.MOVING BETWEEN ROOMS(场景切换)
- 20.ENEMY LOOT DROP(怪物掉落)
- 21.FIXED ENEMY MOVEMENT(修复怪物移动)
- 22.DIALOG(对话)
- 23.IMPROVED DIALOG(对话优化)
- 24.PAUSE MENU(暂停)
- 25.储存游戏的简单方式
- 26.读取存档的简单方式
- 27.发射炮弹Projectiles
- 28.优化人物控制
- 29.优化AI
- 30.简单的音效
相关参考资料:
独立游戏中程序和美术素材线上协同合作的项目同步与备份方案有的嘛?
除了git(用不来)
独立游戏中程序和美术素材线上协同合作的项目同步与备份方案有的嘛?
我以为同步云盘可以,但是不能版本备份...特来请教
寻求能带新人的项目
你好 我是马来西亚华裔 最近因为疫情问题 所以是在家做的 Work From Home 所以比较得空和轻松 本人有学习过少许 Unity
所以多多少少懂一些 本人也是电脑设计行业
精通 C, Python, Kotlin
语言方面 马来文 Bahasa Melay, 英文 English, 中文 (简体, 繁体, 广东话都可以), 日语 (N5 水平 在准备 N4 考试中)
只求有组合项目可以带我 不求薪水 可以从低做起 绝对没问题
可以加微信 和 LiNE 当然也可以 google meet 和 zoom 等等 绝对没问题
游戏出海本地化,助力各类游戏扬帆起航!!!
我们公司成立于 2000 年,主要从事笔译、口译、游戏本地化,短视频翻译等语言服务和各类 IT 服务的跨国企业,目前业内排名全球前五十;全球合作译员大概有30000名,可以翻译全球230种语言合作的知名企业包括:网飞,迪士尼,拳头游戏,美国艺电,腾讯,网易等等,有需要的小伙伴欢迎私聊进群畅聊出海,研发,发行问题或咨询合作,扣扣:619776218;VX:15301140998;
译: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日
晴
SNOWFALL DEVLOG_03
经历了大概10天的严重睡眠不足,白天大部分时间不是补觉就是打瞌睡。接着生病,不过好在一天就恢复了。
基本上这些天都在做3C,发小给的建议是花大量时间在3C上再往后推,要不然可能会有很多返工,可能还要持续一段时间。
之前的准星和移动处理,基本上和nuclear throne差不多,开始测试的时候我用的手柄(像helldivers那样,没有开启准星),忽略了键鼠操作准星可能导致的问题。在键鼠下,当鼠标(准星)固定在某一个点时,角色会在移动的时候根据瞄准角度(鼠标位置)去自动转身,会有点奇怪,就像车在漂移,这个体验很难受。就改成了helldivers那种逻辑,在非瞄准状态下,射击方向和朝向匹配,这样在键盘操作上,如果不瞄准,就是8方向射击,更复古,也可以在不减少移动速度的情况下向一个固定方向射击;如果在比较安全的距离,利用瞄准来精确射击。
精确射击本来打算给敌人设计弱点区,比如这样
但是top-down 3/4 这个视角很尴尬,比如横版卷轴或者3D TPS、FPS来说,敌人和玩家是站在地上,但在2d top-down 这个视角下,其实敌人和玩家实际上是躺在地上 =_=
后来想如果做多个hitbox,在子弹上再加raycast处理来检测hitbox,后来想到如果关联到动画上可能工作量挺大,有点嫌麻烦,暂时先用瞄准线和敌人的中心点之间的角度来处理精确瞄准。后面还是需要试一下raycast,哪怕只给boss或者比较强的敌人做这个功能,这个如果实现了,战斗会更有意思一些,对于枪法好的人来说。
另外学着用动画曲线做了一个完美换弹的试验,不过可能并不用像战争机器那样麻烦,可能Returnal那种处理更简化一些(虽然是建立在无限子弹的基础上),还没有考虑好,暂时没有扔进工程。
………………………………………………………………………………………………………………………………………………………………………………………………………………………………
在写代码和想功能之外,做美术资源的时候开始比较头疼,由于想做非像素,就没办法使用aseprite,用photoshop在做动画上折磨了大概一天,实在是受不了了,找了一些做动画的插件,看视频感觉也不是很方便,就用去年疫情时候买的优动漫的正版开始做角色粗略动画,但是做到一半……发现动画的帧数还有限制,wtf,只好去买了个破解版的Clip Studio Paint EX(功能全部开放),正版我实在是承受不起,在导出的时候CSP只能导出单帧图像,并不像aseprite那么方便,还要用Free texture packer处理成条状图再回photoshop里加颜色,不过整个流程都比只在PS里做动画心情舒畅多了。(顺便说一下,这个软件千万不要像我这样装繁体中文版,很多地方都看不懂,靠猜……还不如在安装的时候直接选择英文了)
………………………………………………………………………………………………………………………………………………………………………………………………………………………………
大前天在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,要花时间仔细学学用法。
(上面俩视频连接都来自Youtube的SamSpadeGameDev,这个频道很不错)
………………………………………………………………………………………………………………………………………………………………………………………………………………………………
2021年11月22日周一
早上阴沉沉 这会儿大太阳
Highway
Gamemaker Studio 2的奇怪小技巧001 - 快速提取Room内instance的各项资料
因为某个游戏项目的需要,需要提取某个特定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 活动 火热报名中!
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 元,具体返现方式可咨询官方淘宝店客服!
战旗冒险,开发日志01
战旗冒险终于完成了初期版本,战斗,房间选择,棋子特殊能力,各种抉择事件等等,能够正常的通关了
现在最大的问题,就是没有美术小伙伴,自己瞎画,看起来非常的闹着玩
游戏主要灵感来自酒馆战旗,战斗也是棋子依次对撞的形式
每回合都会得到金币,经验值,用来提升自己阵容的战斗力
对战双方都有前排和后排,前排单位先攻击,也会优先被敌人攻击.有些棋子可以直接攻击后排
选择不同房间会有不同奖励,有金币,经验,或者直接强化属性
下发的平原2/5,指的是某个地名进入5次会获得更多奖励.
如果大家有更多好点子,也欢迎多提提意见啊
b站视频