译文:Gamemaker Studio系列:2D平台游戏的输入缓冲
作者:Case Portman(@Studio Thunderhorse, 独立游戏 Flynn: Son of Crimson的开发者)
翻译:highway★
“”我按了跳!!
我TM按了跳键,我C%^&#@%#@&*,
你TM倒是跳啊!!!,我%#^@&!"
耳熟么?我注意到很多2D平台游戏手感很好......但是也有很多游戏的手感挺糟的。
2D平台中对手感比较重要的两个东西,一个是“输入缓冲”(input buffering),还有一个是它的哥们儿“土狼时间 ”(coyote time)。
土狼时间是什么呢?就是当玩家走出平台边缘,跳键按得太晚,但是跳跃仍然有效。这是2D平台游戏开发的一个术语。(译注:当然中文搜索基本上是搜不到的,如果你感兴趣,可以google一下,gamasutra上有一些关于跳跃的开发技巧文章会有更详细的解释。)
下面,咱们说说到底什么是输入缓冲。
输入缓冲(Input Buffering)
这个术语用于给输入一个指令(在我们的例子中,按一个按钮)以保留在内存中,直到它能够执行这个指令。最好的例子是在很短的时间内,让玩家可以按下跳跃按钮,一旦他们落地,角色就会跳跃。
让我们通过使用简单的2D平台游戏实现一下上面提到的东西。如果你已经实现了你玩家跳跃代码,那么你很可能是这么做的:“如果玩家在地上 -> 那就可以跳 -> 跳”,这是一个完美的开始,我们现在要做的就是简单的洗洗牌~
//Player跳跃代码
if place_meeting(x, y+1, objSolid) //检测player是否在地上,注:当然你也可以用脚本 { if keyboard_check_pressed(vk_space) //按下空格键 vspeed = -5; //跳起 }
译注:教程这里用vspeed只是为了省事儿,建议自定义速度变量
我们首先要在什么都没开始之前声明两个变量,buffer_counter和buffer_max,后面我可以对其进行调整。
在create事件中加上:
buffer_counter = 0 // buffer_max = 10 //
接下来我们要改一下跳跃代码,加上上面的变量:
//Player跳跃 if (buffer_counter < buffer_max) { buffer_counter +=1; //每帧递增 if place_meeting(x, y+1, objSolid) //检测player是否在地上 vspeed = -5; //跳起 }
我们现在加上触发跳缓冲的按钮。你可以自己选个按键,我在这用了空格键。在step事件中的任何位置添加它,我建议接近上面的代码以保持整洁。
//Player跳跃
if (buffer_counter < buffer_max)
{
buffer_counter +=1; //计数器每帧递增
if place_meeting(x, y+1, objSolid) //检测player是否在地上
vspeed = -5; //跳起
}
//Player跳跃操作
if keyboard_check_pressed(vk_space) //按下空格键
buffer_counter = 0; //重置缓冲计数器
WA-啦!现在,即使你在降落前按下跳跃按钮达10帧,我们的跳跃也会有效,很酷吧?只需几行代码,我们就能让我们的跳跃响应更快。输入缓冲可不是这一个用途,你可以将它添加到你任何喜欢的东西里,如攻击,躲闪,甚至导航菜单。希望这个迷你教程有所帮助!
译文:GAMEMAKER STUDIO基础系列:组合技(Combo)设置
作者:Nathan Ranney
翻译:highway★
接上一期Hitbox&hurtbox,这期我们来看看如何设置一个基本的连击系统。
同样,本文依旧面对新手。
概述:组合技,取消和连续技
在我们开始之前,我想指出有不同种类的组合技。一般来说,一个组合就是当一个攻击一个接一个地连接,你的敌人除了一直挨揍什么也干不了。但是,从第一次攻击到第二次攻击的方式可能会有所不同。
chain combo就是说你的第二次攻击中断或者取消当前的攻击。你的第一次攻击在它所在的位置停止,接着打出第二次攻击。
link combo是当你的第一次攻击完全结束,你的角色恢复到他们的中立状态时,你的对手仍然被击晕足够长的时间让你开始第二次攻击并击中他们。
译注:如果你是格斗游戏玩家,会对这些非常清楚,当然了,如果你在读这篇文章,我会假定你很喜欢动作格斗类游戏,咱们在这儿就不多啰嗦了。
攻击精灵图
我们还是用之前hitbox那篇文章中的精灵图,不过要做一些修改,因为我们要做combo。
我们要加两个新的攻击动作。
sprPlayer_Attack_2
sprPlayer_Attack_3
这些有点小,但这是因为它们是游戏的原生分辨率。我还对之前的原始攻击动画做了一些更改。使用我们的新攻击有点太长了,所以我删除了一堆帧以使其适合我们的新精灵。
sprPlayer_Attack
导入精灵长带图(如果有疑问请看F1),将原点设为16x32
初始化新变量
打开oPlayer的create事件,加入下面代码:
currentAttack = attacks.right; hitLanded = false;
currentAttack将用于根据玩家当前正在进行的攻击来定义不同的行为和动画。 hitLanded将是我们的布尔值,我们用它来告诉我们可以取消当前攻击到下一次攻击。
打开enum_init脚本,让我们为新的攻击添加一些新的枚举。将此代码添加到脚本的底部。
enum attacks {
right,
left,
upper
}
最后,我们需要扩展一下hitbox_create脚本,加一个参数。打开该脚本并将以下代码复制/粘贴到其上。
_hitbox = instance_create(x,y,oHitbox); _hitbox.owner = id; _hitbox.image_xscale = argument0; _hitbox.image_yscale = argument1; _hitbox.xOffset = argument2; _hitbox.yOffset = argument3; _hitbox.life = argument4; _hitbox.xHit = argument5; _hitbox.yHit = argument6; _hitbox.hitStun = argument7; return _hitbox;
我们添加了_hitbox.yHit,这样做可以让我们把敌人打到浮空,而不是仅仅向后击退。
打开normal_state脚本,再最下面加入如下代码:
//reset attack currentAttack = attacks.right;
这可以确保我们的攻击始终从攻击链中的第一次攻击开始。这就是初始化新变量和枚举的全部内容。
扩展switch语句
现在我们已经拥有了所有这些新攻击,我们需要为每个攻击设置动画。使用我们刚刚初始化的新枚举和嵌套的switch语句可以轻松实现这一点。打开animation_control脚本,让我们更改states.attack以包含新的精灵图。
case states.attack:
switch(currentAttack){
case attacks.right:
sprite = sprPlayer_Attack;
break;
case attacks.left:
sprite = sprPlayer_Attack_2;
break;
case attacks.upper:
sprite = sprPlayer_Attack_3;
break;
}
break;
attack_state脚本也要更改:
switch(currentAttack){ case attacks.right: //create hitbox on the right frame if(floor(frame) == 1 && hitbox == -1){ hitbox = hitbox_create(20 * facing,12,-3 * facing,-16,8,1 * facing,0,60); } //cancel into next attack if(attack && hitLanded){ currentAttack = attacks.left; hitLanded = false; hitbox = -1; frame = 0; } break; case attacks.left: //create hitbox on the right frame if(floor(frame) == 1 && hitbox == -1){ hitbox = hitbox_create(20 * facing,12,-3 * facing,-16,8,1 * facing,0,45); //also move forward xSpeed = 3 * facing; } //cancel into next attack if(attack && hitLanded){ currentAttack = attacks.upper; hitLanded = false; hitbox = -1; frame = 0; } break; case attacks.upper: //create hitbox on the right frame if(floor(frame) == 1 && hitbox == -1){ hitbox = hitbox_create(20 * facing,12,-3 * facing,-16,8,1 * facing,-4,45); //also move forward ySpeed = -3; } break; }
我在这里要解释一下。首先,我们完成了与animation_control脚本完全相同的操作,并加了一个switch语句,其中包含每种攻击类型的情况。 hitbox_create脚本已更新为包含我们的新yHit参数。这是倒数第二个值。你可能注意到前两次攻击已将其设置为零。那是因为我们不想在这些攻击中将敌人打到浮空。
其次,我添加了一个检查以允许取消进入下一次攻击。我们检查是否按下了攻击按钮并且hitLanded是否为true。如果满足两个条件,则更改为另一个攻击,将动画帧重置为零,并将hitbox设置为-1。如果hitbox变量未重置为-1,则在下一次攻击发生时不会创建新的hitbox。我们还将currentAttack设置为我们想要取消当前攻击后进入的下一次攻击。
最后,当在第二次和第三次攻击中创建了hitbox时,我们也会对玩家角色加点儿动势。您将看到正在设置xSpeed和ySpeed。在attacks.left情况下,这将使玩家向前移动一点,以确保在第一次攻击击退敌人后让下一击能够击中敌人。在attacks.upper案例中设置的ySpeed会将玩家打到空中,对,就等于是简化出招的隆的升龙拳。
Applying yHit
虽然我们将yHit添加到hitbox_create脚本中,但还没应用于敌人。在oEnemy对象中,打开end step事件,让我们加点儿东西。
//get hit
if(hit){
squash_stretch(1.3,1.3);
xSpeed = hitBy.xHit;
ySpeed = hitBy.yHit;
hitStun = hitBy.hitStun;
facing = hitBy.owner.facing * -1;
hit = false;
currentState = states.hit;
}
设置和重置hitLanded
最后一步,打开oPlayer的end step事件,当我们的hitbox打中敌人时,把hitLanded设为true:
//hitbox
if(hitbox != -1){
with(hitbox){
x = other.x + xOffset;
y = other.y + yOffset;
//collision check
//checking the collision from the hurtbox object
with(oHurtbox){
if(place_meeting(x,y,other) && other.owner != owner){
//ignore check
//checking collision from the hitbox object
with(other){
//check to see if your target is on the ignore list
//if it is on the ignore list, dont hit it again
for(i = 0; i < ds_list_size(ignoreList); i ++){
if(ignoreList[|i] = other.owner){
ignore = true;
break;
}
}
//if it is NOT on the ignore list, hit it, and add it to
//the ignore list
if(!ignore){
other.owner.hit = true;
other.owner.hitBy = id;
owner.hitLanded = true;
ds_list_add(ignoreList,other.owner);
}
}
}
}
}
}
打开normal_state脚本并将其添加到脚本的末尾。
//reset hit landed
hitLanded = false;
这将确保在攻击发生之前始终将hitLanded设置为false,因为我们的攻击始终从normal_state脚本启动。
运行游戏并开始打击你的对手!如果你用不同的按键对应3种攻击,你的角色应该执行一个由三种攻击组成的chain combo~
感谢阅读~
译文:GameMaker Studio基础系列:Hitbox和hurtbox(攻击/受击判定盒)
作者:Nathan Ranney
翻译:highway★
注意:本文面向初学者!!!
原文部分函数为GMS,译文中已经改为GMS2的对应函数
观看操作视频或阅读
这篇博客文章概述了设置hitbox和hurtbox的所有步骤和代码。您也可以按照以下视频进行操作:
https://youtu.be/NbOVd4ycZkg (要爬出去)
什么是hitboxes和hurtboxes?
注:这里我还是用原文,原意是攻击判定盒 & 受击判定盒,如果你常玩FTG、ACT或者FPS类型的游戏,对hitbox这玩意儿肯定了解很多。不了解的朋友还请仔细阅读。
基本上,hitbox和hurtbox只是专门的碰撞检测(碰撞检测允许您确定对象何时接触或重叠)。hitbox通常与某种形式的攻击相关联,并描述该攻击的有效范围。hurtbox通常与角色(或游戏中的任何其他“可击中”对象)关联。每当他们俩碰撞时,我们认为攻击已“达成”,我们将其效果应用于目标。下面的内容我会用FTG类型游戏做主要的例子。在我看来,格斗游戏提供了最明显的hitbox和hurtbox示例,使它们非常容易理解。 我们来看看街霸4,如下:
上图里,我们看到Makoto表演了她的一个特殊动作,吹上攻击。这招儿就是向上出拳,通常用来防空,可以击中向你跳跃的对手。红色矩形是hitbox,而绿色矩形是hurtbox。如果Makoto用她的hitbox碰到别人的hurtbox,那么另一个玩家将被“击中”。
现在,默念“box”一千遍,好了,咱们开始设置。
Hurtbox 设置
首先!我们需要一个精灵图用于我们的hurtbox。创建一个新的sprite,命名为sprHurtbox,1 x 1像素,并将其着色为绿色。我们只需要一个像素,因为我们将在实例化hurtbox时将其缩放到我们需要的任意大小。另一种方法是为每个可能需要hurtbox的游戏对象创建一个自定义大小的精灵图,这样很……浪费资源,也很……无聊。
现在我们创建一个object(对象),命名为oHurtbox,精灵图指定为sprHurtbox。添加create事件,敲下面这些。
image_alpha = 0.5; //让hurtbox半透显示 owner = -1; //将绑定到创建它的任意对象的id,比如oPlayer xOffset = 0; //用来跟owner对齐位置 yOffset = 0; //同上
现在我们需要创建一个hurtbox并给它一个持有者。
创建一个script(脚本),命名为hurtbox_create。敲入下面这些代码。(咳咳,哥们儿你别复制粘贴啊……)
_hurtbox = instance_create_layer(x, y, layer, oHurtbox); //创建oHurtbox对象,注,如果你想用其他的layer来显示这玩意,就把layer改为你想要显示的layer名,“layer name” _hurtbox.owner = id; //存储该对象的id _hurtbox.image_xscale = argument0; _hurtbox.image_yscale = argument1; _hurtbox.xOffset = argument2; _hurtbox.yOffset = argument3; return _hurtbox;
如果你以前没怎么写过script(脚本)的话,可能觉得这个看起来有点儿多啊,但其实很简单。首先,我们创建一个oHurtbox对象,并将该对象的ID存储在_hurtbox的owner变量中。然后,使用_hurtbox的变量,我们传入所有者(调用此脚本的任意对象),接着定义了hurtbox的大小和偏移量。现在脚本已经写好了,我们可以来调用一下试试看。打开oPlayer对象把下面的代码加到create事件里。
//hurtbox hurtbox = hurtbox_create(18,24,-9,-24); //hitbox hitbox = -1;
使用我们刚刚创建的hurtbox_create脚本,我们可以很方便的地设置比例和偏移量,并将oHurtbox对象的ID存储在oPlayer对象可以使用的变量中。脚本中使用的数字以像素为单位。我们创建的hurtbox是18像素宽x24像素高,偏移玩家精灵左侧9像素,并偏移玩家精灵上方24像素(注:这说的可够真详细的 =_=)。好了,现在运行游戏看看,hurtbox好像没有跟随你的角色。
我们得解决这个问题。在oPlayer对象中打开end step事件并添加以下代码。如果你看了本系列的前几篇教程,我把这些代码加到了animation code底下。
//hurtbox with(hurtbox){ x = other.x + xOffset; y = other.y + yOffset; }
with和other如果你还没用过的话,我在这里简单解释一下(如果还是不明白的话,还是去仔细看一下F1比较好)。当你使用with后跟对象名称(或特定对象ID)时,花括号里的代码将执行,就像该对象正在执行它一样。so,当我们写with(hurtbox)时,我们正在更新存储在hurtbox变量中的特定oHurtbox对象的x和y位置。
由于我们使用with,我们也可以使用other。这段代码用到other时,它将引用此代码运行的原始对象。在这种情况下,就是我们的oPlayer对象。
好了,现在hurtbox跟随玩家了。
Hitbox设置
现在我们有hurbox了,我们得打它啊! hitbox所需的设置跟hurtbox差不多,但它还有更多功能。 简单理解一下hitbox,首先我们来检测碰撞,要是碰撞了,然后决定接下来要做什么。(哎,我车还没碰到你,你怎么倒了呢?面对一些老年碰瓷者,有可能是咱们的车hitbox出毛病了,要么就是他们的hurtbox的offset或者scale出毛病了吧……这时候可能就需要交警和行车记录仪来debug了 =_=)
就像hurtbox一样,我们需要创建一个精灵和一个对象。创建一个名为sprHitbox的单像素精灵,红色。然后创建oHitbox对象并指定sprHitbox精灵。添加create,step,end step和destroy事件,打开create事件并敲入以下代码。
image_alpha = 0.5; owner = -1; xOffset = 0; yOffset = 0; life = 0; //hitbox存活时间 xHit = 0; //用来击退 x方向 yHit = 0; //用来击退 y方向 hitStun = 60; //击晕时间 ignore = false; ignoreList = ds_list_create();
与我们的hurtbox一样,我们需要设置所有者和偏移量。然而,与受伤害的盒子不同,hitbox并不是一直存在的,它只存在于攻击期间。life变量将用于确定数据框将存在多少帧并保持活动状态。 xHit和yHit是我们的击退变量。hitStun确定我们击中的角色被打中后眩晕的时间。最后,ignore变量和ignoreList列表将用于确保我们不会多次击中一个角色。后面你会看到它是如何工作的。
击中眩晕是一个角色在被击中后被击晕的时长。如果玩家被击晕,除了等着被揍或者祈祷,他们什么都做不了(当然你也可以写成疯狂按键可以稍微减少眩晕时长)!格斗游戏里这玩意儿很常见。你要是把对手打晕了的话,嗯……先来一个挑衅动作,然后一套连招KO好了~ (或者…你也可以点一个轻攻击让对方恢复正常,接着继续干死他…有点儿更藐视对手,是的,我跑题了 : p)
打开destroy事件并加上下面的代码。
owner.hitbox = -1; ds_list_destroy(ignoreList);
这可以确保hitbox在销毁后,其所有者停止尝试与其进行交互,并在不再需要时删除ignoreList。如果列表未被删除,则可能导致内存泄漏。
之后打开step事件,加入下面代码:
life --;
这将在hitbox处于活动状态时从生命周期中减去(就是计时器)。当life变量达到0时,删除hitbox。最后到end step事件,加入下面这一小段:
if(life <= 0){ instance_destroy(); }
当一个对象被破坏时,就像我们上面所做的那样,将调用destroy事件(如果存在)。OK,hitbox设置已经完成了, 但对于实际对象!还有很多事情要做。就像hurtbox一样,接着我们要干嘛?对了,脚本。创建一个新脚本,命名为hitbox_create,然后敲入以下代码(上面的我加了注释,下面的注释我就不加了,作者讲的很细)。
_hitbox = instance_create_layer(x, y, ,layer, oHitbox); _hitbox.owner = id; _hitbox.image_xscale = argument0; _hitbox.image_yscale = argument1; _hitbox.xOffset = argument2; _hitbox.yOffset = argument3; _hitbox.life = argument4; _hitbox.xHit = argument5; _hitbox.hitStun = argument6; return _hitbox;
跟hurtbox那个差不多,多了几样东西,life,xHit和hitStun。 完事儿了吗?我们差不多已经完成了一半。回到oPlayer对象的end step事件,在hurtbox代码段下面加上
这些:
//hitbox if(hitbox != -1){ with(hitbox){ x = other.x + xOffset; y = other.y + yOffset; } }
这与hurtbox代码略有不同,我们要先在攻击那一刻确认此时是否已经有hitbox存在,也就是检查我们的hitbox变量是否不等于-1。
现在,最后一步,我们需要在攻击期间的正确时间实际创建hitbox。但在我们这样做之前,我需要简要介绍一下格斗游戏中攻击的实际构成。所有攻击都分为三个部分。启动(Start up),活跃(Active)和恢复(Recovery)。每一部分都会持续一定的帧数。看看下面的图表会理解的更清晰。
(译注:这个图不翻译了,gif弄着太麻烦了,见谅,但是这个图是精华,一定要仔细看懂)
启动是攻击变为活动所需的时间,然后衔接到出拳。活跃是hitbox能够实际击中敌人的时间。恢复是角色完成攻击并返回中立状态(译注:这里对状态不太熟悉的话,请先详细了解一下状态机)所需的时间,之后才可以再执行其他操作。让我们看看我们的角色精灵,以确定我们的启动,活动和恢复帧应该在哪里。
我们的启动帧是0-2帧。相当于攻击动作的发条。活跃帧为3-4帧,恢复5-7帧。我们需要在第3帧创建我们的hitbox,它需要在第5帧开始之前一直处于活动状态。在我的项目中,我的frameSpeed变量为0.15并且游戏以60 fps运行,我的精灵动画以大约每秒四帧。所以,我的hitbox的生命需要为8帧。
打开attack_state脚本并添加以下行(译注:这个脚本在之前的教程中)。
//在合适的时间创建hitbox if(frame == 3 && hitbox == -1){ hitbox = hitbox_create(20 * facing,12,-3 * facing,-16,8,3 * facing,45); }
我们要检查我们是否在正确的帧上,并且hitbox不存在,再使用hitbox_create脚本创建hitbox。在创建hitbox时,我们需要将水平值(xscale和xOffset)乘以角色面向的方向。这确保了hitbox始终与角色的方向对齐。然后我们设置了8帧的存活时间,然后是水平击退和击晕。现在运行游戏并按下攻击,你应该会看到hitbox出现并按预期消失。现在我们得让它能打东西了!
TIPS:hitbox越大,它就越强大。生活也一样。 hitbox活动的时间越长,它就越强。在格斗和动作游戏中,巨大的,持续时间长的hitbox总是非常强大(想想那些恶心人的BOSS吧)。在设计攻击时请记住这一点!
敌人设置
拳击手需要沙袋,而我们,需要一个敌人。这将非常简单,因为敌人将使用与我们的玩家相同的许多代码(译注:通常玩家和敌人会隶属于一个Entity的父类对象,这样就不用重写类似的代码了)。现在,我们需要添加一些新的精灵。你可以使用你想要的任何精灵,或者用我正在使用的相同精灵(译注:效果可能没那么好,比如受击、跳)。
以与创建玩家精灵相同的方式创建精灵。确保精灵原点是(16,32),就像上次一样!你应该有两个精灵:sprEnemy_Idle和sprEnemy_Hurt。 复制oPlayer对象并将其命名为oEnemy(译注:如果你有一个oEntity的对象的话,就可以更方便了)。将sprEnemey_Idle sprite分配给对象,然后打开create事件。我们需要添加一些新变量:
hit = false; hitStun = 0; hitBy = -1;
hit是一个简单的布尔值,我们将在应用命中效果时使用到它。接下来,hitStun是被击中后敌人在hitStun中停留的时间。最后,hitBy将是击中它们的对象的ID。
接着打开step事件。删除与player按键和状态切换有关的代码段(译注:如果你有一个oEntity的对象的话,没必要这么麻烦了)。当我们按下按钮时,我们不希望敌人执行动作,我们需要重写状态切换。加入以下代码。
//状态切换 switch currentState { case states.hit: hit_state(); break; }
由于我们的敌人只会站着或被击中,我们现在不需要任何其他状态。但是我们确实需要创建hit_state脚本。立即执行此操作并添加以下代码。
xSpeed = approach(xSpeed,0,0.1); hitStun --; if(hitStun <= 0){ currentState = states.normal; }
如果你已经读到这里了,那这对你来说应该很熟悉。首先,我们降低敌人的水平速度,直到达到零。接下来,我们让hitStun倒计时,并在hitStun达到零时将敌人恢复到默认正常状态。很简单吧! 再打开end step事件。首先,把animation_control()改成 animation_control_enemy();然后在hurtbox代码下面添加这个。
//被打了~~ if(hit){ squash_stretch(1.3,1.3); xSpeed = hitBy.xHit; hitStun = hitBy.hitStun; facing = hitBy.owner.facing * -1; hit = false; currentState = states.hit; }
这是我们应用命中效果的地方,如击退,挤压和拉伸,屏震(如果你想要这种效果的话),等等。它还将敌人状态更改为受击状态,这会阻止他们在击中昏迷时执行任何其他操作。 现在,我们要创建animation_control_enemy脚本。这是玩家使用的相同类型的脚本,但是简化了,因为敌人的动画和行为比玩家少很多。加入下面的代码(注意精灵名是否与你的资源匹配):
xScale = approach(xScale,1,0.03); yScale = approach(yScale,1,0.03); //animation control switch currentState { case states.normal: sprite = sprEnemy_Idle; break; case states.hit: sprite = sprEnemy_Hurt; break; } //reset frame to 0 if sprite changes if(lastSprite != sprite){ lastSprite = sprite; frame = 0; }
这里没什么好说的。我们所做的就是根据状态设置精灵,就像我们对玩家一样。 OK,敌人设置完成!放在房间里一两个敌人。下面到了比较难的部分了...检查hitbox / hurtbox碰撞(重叠),并解决该碰撞。
击中检测和确定攻击
这部分有点儿绕。还记得with和other么?嗯...我们还要用到它们,但嵌套在自己内部。告诉对象在另一个对象内部的另一个对象内部做什么!对象开始!好吧也许它并不复杂,但有时读起来就有点儿费劲...... 不管怎样,咱们先回到oPlayer对象并打开end step事件,你可以在其中更新一下hitbox代码段,让它看起来像这样。
//hitbox if(hitbox != -1) { with(hitbox) { x = other.x + xOffset; y = other.y + yOffset; //check to see if the hurtbox is touching your hitbox with(oHurtbox) { if(place_meeting(x,y,other) && other.owner != owner) { //do some stuff } } } }
快速回顾一下这里发生的事情。我们检查当时是否确实有一个hitbox,如果有,我们会检查所有的hurtbox对象,看看它们是否与这个特定的hitbox实例发生碰撞。使用with时请务必注意,如果您只使用对象的名称(如oHurtbox)而不是对象的实例ID,则将从该对象的所有实例中运行代码。现在我们是两层深,并且正在检查来自hurtbox的碰撞,所以当我们使用other时,它不再引用运行所有这些代码的主对象(oPlayer对象),而是作为一个层的对象在这一个之上(oHitbox对象)。 查看下面的图表,可以直观地了解正在发生的事情。
oPlayer用于与oHitbox通信,然后oHitbox使用with与oHurtbox进行通信。每次调用都会为代码创建一个新层。当一个对象正在使用其他对象时,它会引用它上面的层。必须了解这些层以及with/other,才能完全理解这些碰撞检测将如何工作。
最后,我们需要解决碰撞。我们已经检查了hitbox和hurtbox是否发生了碰撞,现在我们需要决定接下来会发生什么。好了,我们的ignore、ignoreList登场啦。首先,我们需要检测,看看hitbox是否已经击中了hurtbox。
//hitbox if(hitbox != -1) { with(hitbox) { x = other.x + xOffset; y = other.y + yOffset; //检测hurtbox是否碰到了hitbox with(oHurtbox) { if(place_meeting(x,y,other) && other.owner != owner) { //ignore检测 //检测来自hitbox对象的碰撞 with(other) { //检查你的目标是否在忽略列表中 //如果是,不要再次击中它 for(i = 0; i < ds_list_size(ignoreList); i ++) { if(ignoreList[|i] = other.owner) { ignore = true; break; } } } } } } }
好多花括号......甭担心。在确定我们的hitbox与一个hurtbox相撞后,我们不得不再做一个功能,并且这两个判定盒有不同的Owner(持有判定盒的对象)。Owner检查可防止hitbox与属于玩家的hurtbox碰撞,从而阻止玩家自己打到自己。
接下来检测我们要忽略的敌人列表。如果你之前从未使用过for循环,这可能会让人感到有些困惑,但它看起来要简单得多。 for循环执行一定代码块一定次数。在这儿,它执行的次数与ignoreList中的数据实例一样多。它会检查列表中的每个位置,并将其与刚刚碰撞的hurtbox的owner进行比较。如果列表中的任何数据与hurtbox的owner匹配,则忽略owner,并且不会被命中,我们将使用break停止检查列表。我们这样做是为了防止同一个敌人在我们的攻击活跃的每一帧被击中。如果这个忽略检测不存在,则敌人将在8帧中被击中8次。
你可能想知道ignoreList 如何填充数据,我们后面再说。如果我们的第一次检查失败,也就是说,如果不应忽略敌人,我们可以击打它们并将其数据添加到列表中。对你的代码进行以下更改。
//hitbox if(hitbox != -1) { with(hitbox){ x = other.x + xOffset; y = other.y + yOffset; //check to see if the hurtbox is touching your hitbox with(oHurtbox){ if(place_meeting(x,y,other) && other.owner != owner){ //ignore check //checking collision from the hitbox object with(other){ //check to see if your target is on the ignore list //if it is on the ignore list, dont hit it again for(i = 0; i < ds_list_size(ignoreList); i ++){ if(ignoreList[|i] = other.owner){ ignore = true; break; } } //if it is NOT on the ignore list, hit it, and add it to //the ignore list if(!ignore){ other.owner.hit = true; other.owner.hitBy = id; ds_list_add(ignoreList,other.owner); } } } } } }
如果ignore为false,那么hurtbox(other.owner)的持有者就会被击中!我们需要告诉它被击中的对象(other.owner.hit = true)以及击中它们的对象(other.owner.hitBy = id)。然后将它们添加到忽略列表中,这样我们就不会在下一帧再次点击它们(ds_list_add(ignoreList, other.owner)。现在运行游戏,去揍你的敌人们吧!他们应该会被击中、击退、并被击晕(译注:当然,在动作或格斗游戏中,单一的普通攻击是不会击晕敌人很长时间的,这里的敌人硬直时间可能稍长,击退距离也略显长,这里是教程作者为了便于理解有意为之)。
最后的想法
哇。好累啊!很高兴你能看完,真棒~ 当我开始写这篇文章时,我并没有预料到需要这么长时间(译注:嗯,别说写了,我都没想到要花这么长时间来翻译,真的很累……)。我很高兴能够展示很多有趣的概念,例如with / other,for循环,ds_lists和简单的碰撞检测。
我非常感谢你花时间阅读这篇文章,我希望你能学到新东西。你可以在Twitter上关注我,并在我的网站上关注更多与游戏开发相关的内容!有关本文中介绍的一些新主题的更多信息,请查看以下链接。
重要链接
- Part 1 of GameMaker Basics: Drawing Sprites
- Part 2 of GameMaker Basics: State Machines
- Part 3 of GameMaker Basics: Juicing Your Movements
- Sprites and animation by Alexander Prokopiev
- GameMaker final project file
- Ds_lists in GameMaker
- About the With function in GameMaker
- Keywords in GameMaker
- Loops in GameMaker
- AABB (Axis-Aligned Bounding Box) Collision Detection
嗯,我就是,又帅又爱分享的Nathan
GameMaker Studio2 文档翻译征集校对人员
通过之前的文章《...文档翻译 需求调查》,目前已经征集到了包括我在内的5名翻译人员,目前进展良好。
为了保证质量和速度,并让项目最终完成,我决定采用小批量的工作方式,即翻译一章(包括很多节)马上校验一章,以确定翻译的质量,并且避免多次返工。
然而文本量巨大,翻译人员仍然显少,质量和速度并不能兼顾,因此需要再召集3-5名校对人员,要求如下:
- 经常使用GMS2,或正在使用,并比较完整的翻阅过gms/gms2的文档。
- 有一定英文阅读能力,不要求翻译。
- 需要根据英文文档和翻译好的中文文档,指出并记录翻译的问题。
参与校对流程
通过邮箱(deciia@qq.com)或者QQ(317937401)联系我申请。
以下是作为参考的中英文档:
英文文档 中文文档
一般来说每完成一个章节都会发布到网站上,每个月会进行一次完整的对外发布。
你可以尝试选择左测目录中已经翻译的一章,如“附加信息”,阅读一遍中文,感受一下是否阅读流畅,是否有难以理解的地方,然后对照一下英文,查看是否有翻译错误。
将你的感受和建议直接在原文下方回复。
正式参与后,加入翻译组,之后的过程中记录你完成的工作,项目最终完成时,和其它组员一起添加到贡献列表。
校对流程如下:
- 下载看板管理软件Trello(应用市场可以下载,网页版),并提供你的常用邮箱,通过邮件邀请的方式,我会将你添加到翻译项目中。
- 翻译项目的“待校验”列表是初步完成翻译的章节,选择一个标记为自己,具体操作群里会说明。
- 每周用一段时间,或平常在使用gms2文档的时候,校对在Trello翻译项目对应的章节的评论中记录你感觉不流畅、难以理解、翻译错误的情况。
- 完成校对后,将相关的章节移动到“在修改”中。
- 重复2-4步,直到全部完成。
可以开两个浏览器窗口对照。
如果你觉得这样不够爽,也可以使用翻译的流程,在omegaT中,每句都有中英文对照。详细可以查看翻译注意事项。
翻译流程需要下载omegaT,注册github帐号,通过测试项目测试与github的同步,正式开始之前的过程稍微有点麻烦,但之后只要核对记录就可以了。为避免误操作,不推荐新手使用这种方式。
接下来是画饼时间了
目前除了人员的参与之外,并没有什么开销,翻译API目前是免费的。但是想做得更好的时候还是会考虑一下成本,比如某仙女用了几个小时去寻找并试用免费的资源做runtime的rss。有很多构想都是基于两点,一是有人来做,然后是人支持。人的想象是无限的,能做到的事情是有限的。更大的饼在这里说了也是空话。
如果觉得对你有帮助,可以献出一份支持,这些并不足以支付时间上的付出,但足够画一张大饼。之后,请捐赠者在留言中说出你的想法,为什么会捐赠,遇到了哪些问题,还有什么需要帮你做的,务必…务必留下邮箱或其它的联系方式,以便我们可以对你的体验进行回访。不方便表明身份的话,可以直接联系我,最好注明一下自己是捐赠者。
捐赠时请备注:gms2文档翻译
GMS用SteamSDK No.4 找到创意工坊下载文件
当玩家在创意工坊里面下载了东西 不管是皮肤 还是地图等 都是在一个个文件夹里面而已
找到对应的位置 然后自己内容 验证就行了 过程我懒得管 我就教 如何找到位置而已
然后在意的就是 我写代码的时候 会忽略steamAPI 启动验证等玩意
首先 给你们看一下分析结果的内容
一个list 两个 Map
1.用户订阅内容的ID列表
2.订阅内容下载状况
3.订阅内容的信息
估计直接一个创建能够写完吧
讲一下目的性 我要做的内容是 筛选 能够使用的 创意工坊物品的 文件架 载入这个list 里面
FileList = ds_list_create();
首先我们要获 用户订阅内容的ID列表
IDList = ds_list_create(); steam_ugc_get_subscribed_items( IDList );
把信息塞入 map里面面 筛选 然后 加入文件列表里面
for (var i = 0; i < ds_list_size(IDList); ++i) { //创建和载入 var Update = ds_map_create() steam_ugc_get_item_update_info(IDList[|i],Update); var Install = ds_map_create(); steam_ugc_get_item_install_info(IDList[|i],Install); //这里 执行筛选 if Update[?"is_installed"] //验证是否安装 if !Instanll[?"Legacy_item"] //google那边翻译是遗留文件 { ds_list_add(FileList,Install[?"folder"]);//文件夹的位置就在这里了 } //清理残渣是个好习惯 ds_map_destroy(Install); ds_map_destroy(Update); }
GameMaker Studio2文档翻译需求调查
^_^喵
GMS2界面翻译
在和yoyo官方确认之后,得知官方并不反对民间进行学习目的的本地化工作,最初的版本是由我、小太和LiarOnce共同完成的,接收到群里有人在求最新的界面汉化的反馈,我用了大约两周的时间,完成了后续的翻译并进行了多次校验。我不确定的是,这样的工作能帮助到多少人,仅仅有一人反馈就动用这么大的时间量来做这件事,还是有欠思考。
不过已经完成了,这个项目就不多说了。
这是项目地址,请阅读Readme并往指定地址下载相应的版本。
项目地址 发布页面 最新版本2.2.0.341
GMS1&2文档翻译
从1代开始我就在做这件事,也尝试召集了汉化组,但都没有最终完成。一个给大家使用,而不是仅仅是个人参考的文档,对于gms2的学习和推广是有必要的。我没有继续翻译的原因主要原因:
- 我不确定多少人对这个有需求,我个人阅读英文文档没有太大压力。没有接收到持续的反馈让我能够了解到是哪些人真正在使用这个引擎。
- 汉化组的人都很忙,而且并不都是一直在使用GMS,或者说对GMS的汉化的需求也不大,仅仅靠催促来推动工作量。
我现在已经做好了准备,微软翻译API已经申请到了(使用全币种信用卡申请的,测试了几个,只有微软的能用),可以尝试继续这件事。
在开始之前,有一些前提需要确认。
- 文档翻译这件事值得做吗,有多少人需要这个东西?
- 我们需要用多大的成本来做这件事,多长时间,需不需要使用商业工具和人员的辅助,有没有外援?
- 汉化组的质量是否可以得到认可,需要花多大的精力或额外的成本来提高自己?
对于每一个使用引擎开发游戏的人来说,英文阅读能力是非常重要的,很多技术方案的解决方案和教程来自英文环境,真正想用好GMS的人对汉化版本的需求有多大?即使不考虑这个,具体有多少会使用gms2开发,汉化好了又能让多少人开始使用gms2,这些都是不确定的数字。
来自红激的数据,他接触到的长期使用gms2的大约100位左右。
如果你确实需要一份高质量的人工翻译的文档以及其它更多的基础教程
请在原文中留言,也欢迎给我发邮件(deciia@qq.com),或者在网站上联系我
你可以说明:
- 说明自己需要汉化版的原因
- 对要翻译的内容进行一个简单排序,即你想优先了解哪些文档中的内容。
- 是否方便后续的回访,以及其它的需求,你要分享给我的内容。
如果知道朋友有汉化文档的需求,如果你或者他曾经尝试过翻译gms2文档,向他说明:
http://valcell.com/blog:68,这篇文章在征集GameMaker Studio2 文档翻译需求的反馈,你不是想要汉化的文档吗/你不是也在翻译吗?
或者简单的通过下面的分享工具将本文的地址分享给他/她。
我们的目标是:找到需要翻译文档的人,正在使用GMS2构建项目并遇到问题的人,以及愿意提供助力,让这件事有较大可能性完成。
目前已经联系上了顺子,如果大家还知道哪些正在孤立的做这些工作,肯请将相关的页面或联系方式在评论中回复或给我发邮件。
上面我说到了成本,并不是说将来需要大家为此支付什么,而是希望大家认识到,要想获取更有价值的东西,需要支付时间,支付信任,一份简单的邮件或反馈,一个可以帮助产品持续改进的过程,一句简单的感谢,以及偶尔也可以为别人做点什么。
目前的翻译流程
目前是使用omegaT软件辅助翻译,它有以下优点:
- 它会将文档分割成许多片段,提供方便的界面和快捷键,加快翻译的效率,官方文档更新后,也能自动检测未翻译的片段,并用记忆库已经完成的片段进行匹配,匹配成功的直接替换,不成功的也会给出模糊匹配的建议。
- 翻译之后生成的记忆库可以转移到其它的gm/gms/gms2的文档项目,比如我将gms的记忆库转移到gms2项目中,很多已经翻译的都不需要再翻译了。
- 词典,术语词汇表,机器翻译,模糊匹配,注解,笔记,多选译文,支持许多人性化的工具,以提供翻译的参考。
一个公用的记忆库,对于所有的gms相关的翻译项目来说,都是一笔财富。
详细可以参考:翻译注意事项
我可以期待
最后,要说明的是,这是一个期望完成一个高质量gms2文档翻译的实验。需要确认大家对这件事有多重视,以免我们不会多做无用之功。
GMS用SteamSDK No.4 找到创意工坊下载文件
当玩家在创意工坊里面下载了东西 不管是皮肤 还是地图等 都是在一个个文件夹里面而已
找到对应的位置 然后自己内容 验证就行了 过程我懒得管 我就教 如何找到位置而已
然后在意的就是 我写代码的时候 会忽略steamAPI 启动验证等玩意
首先 给你们看一下分析结果的内容
一个list 两个 Map
1.用户订阅内容的ID列表
2.订阅内容下载状况
3.订阅内容的信息
估计直接一个创建能够写完吧
讲一下目的性 我要做的内容是 筛选 能够使用的 创意工坊物品的 文件架 载入这个list 里面
FileList = ds_list_create();
首先我们要获 用户订阅内容的ID列表
IDList = ds_list_create(); steam_ugc_get_subscribed_items( IDList );
把信息塞入 map里面面 筛选 然后 加入文件列表里面
for (var i = 0; i < ds_list_size(IDList); ++i) { //创建和载入 var Update = ds_map_create() steam_ugc_get_item_update_info(IDList[|i],Update); var Install = ds_map_create(); steam_ugc_get_item_install_info(IDList[|i],Install); //这里 执行筛选 if Update[?"is_installed"] //验证是否安装 if !Instanll[?"Legacy_item"] //google那边翻译是遗留文件 { ds_list_add(FileList,Install[?"folder"]);//文件夹的位置就在这里了 } //清理残渣是个好习惯 ds_map_destroy(Install); ds_map_destroy(Update); }
GMS2:draw_text_ext小坑一枚
大多数情况下,我们做游戏总要用到文字,而大量的文字显示就需要考虑排版。
文字最基本的排版手段就是换行,那么我们该怎么在游戏中让文字自动换行呢?
如果看过一些教程的朋友应该都知道再 GMS2 里普通的文字绘制函数是:
draw_text(x,y,string)
而另外还有一个略微高阶一点的函数是:
draw_text_ext(x, y, string, sep, w);
理论上,这个带 ext 后缀的函数就是自带的具备自动换行功能的函数了,其中“sep”即行间距,而“w”正是设定的单行文字像素宽度,理论上当文字长度超过这个宽度时就会自动换行,许多关于对话框实现、文字显示的教程里也都会使用这个函数,但是当你自己去尝试使用这个函数时却往往会发现,自动换行并没有起效……
记得最早在 indienova 上有人介绍 FriendlyCosmonaut 的对话框教程时下面就展开过讨论,后台也有朋友提过这个问题,不过当时一直没明白原因,因为测试的时候发现有的时候可以,有的时候又不行,非常不稳定。直到有一天 QQ 群里的朋友一语道破:
因为根据拉丁语系都是以单词为单位组成句子的,而单词跟单词之间必有空格,为了保证单词完整可读,所以 draw_text_ext 这个函数是在当单行文字宽度超过了你设置的限制并且有空格时才进行换行
而我们中文的字与字之间不需要空格,于是就出现了这种很神奇的无法自动换行的“BUG”解决的方法其实也简单,第一种最简单的就是你在本该换行的地方敲上一个空格,让这个函数发现此处有空格该换行了。但这种做法只适合做测试或者一两处文字随手改一下的情况,如果文字量大,而且时不时需要重新调整排版,那工作量就非常感人了。
所以,我们还有一个办法,自己写一个脚本来给中文自动换行即可,此处 QQ 群里的“口十”同学贡献了自用的一个脚本可供诸位参考,其中还针对行末是标点符号的情况做了特殊处理,确保把标点符号留在行尾(不过符号不够完整,各位可根据自己的使用需求进行修改调整)。
/// @func draw_chinese_text_ext(x, y, str, sep, w, less_or_more);
/// @param x_ordinate x 坐标
/// @param y_ordinate y 坐标
/// @param string 绘制的字符串内容
/// @param sepration 行间距
/// @param width 单行最大像素宽度
/// @param l_or_m 单行取大于或小于上述宽度
var xx = argument[0];
var yy = argument[1];
var str = argument[2];
var sep = argument[3];
var w = argument[4];
var l = noone;
if (argument_count > 5) l = argument[5] else l = true;
var x0 = xx;
var y0 = yy;
while!(str == ""){
var s = "";
var i = 0;
var ww = 0;
var con = "";
do{
i++;
s = string_copy(str, 1, i);
ww = string_width(s)
con = string_copy(s, i, 1);
}until(ww >= w) or (i >= string_length(str)) or (con == "#");
if (con!="#"){
if (l) and (ww > w){
var char = string_char_at(s, string_length(s));
var t = char == ")" or char == "," or char == "。" or char == "!" or char == "?";
if !(t){
i--;
s = string_copy(str, 1, i);
}
}
draw_text(x0, y0, s);
y0 += sep;
str = string_delete(str, 1, i);
} else {
s = string_copy(str, 1, i-1);
draw_text(x0, y0, s);
y0+=sep;
str = string_delete(str, 1, i);
}
}
return noone;
[ 分享 ]一起做牧场物语---001移动&碰撞 [GMS2中文教程]
B站地址(诺娃只能播放流程画质,调整画质请前往B站)
英文教程地址:Movement and Collisions | Farming RPG Tutorial: GMS2 [1]
大家好!我又出现了!!
本期视频,从一个初学者的角度,介绍了有关移动/碰撞的制作方法
参照牧场物语制作了简单的移动(伪4方+按键跑步+传送带),还有就是普通的方块碰撞实现(Step事件内)
希望大家喜欢!
下一期会带来素材的整理及素材的使用~~欢迎继续大家继续关注并提出宝贵的意见和建议.
[ 分享 ]一起做牧场物语---000基本介绍 [GMS2中文教程]
B站地址(诺娃只能播放流程画质,调整画质请前往B站)
英文教程地址:Farming RPG Tutorial: GMS2 [Intro for Beginners]
大家好!这里是一只二爷,作为一名GMS2的初学者,在油管看了很多的教程视频,为了更多的人能接触这个好软件!决定争取抽空把好的视频自己理解后中文化,可能有很多不尽人意的地方,请大佬们多多指教!
这次的视频主要内容是制作一个牧场物语范本系列视频,原创意来源于Youtube的FriendlyCosmonaut.预计会有20个左右的中文视频及主要的gml代码全注释共享,希望大家喜欢支持!
请教一下关于游戏暂停的问题
我刚接触GMS2不久,正在B站学习大佬的《横板射击游戏教程》,学到游戏暂停的时候遇到了一个问题,
因为视频原作者用的试用版GMS2,无法调用surface函数,所以,在说到使用application_surface获得截图后就没有深入了。。
之前视频教程里有通过设置摄像机,将640*360的游戏,放大成窗口1280*720的游戏,
但是问题来了。。
因为视频用黑屏来作为暂停不是很喜欢,于是自己还是用的application_surface获取截图来作为暂停画面,
代码如下:
if !pause_{
pause_ = true;
instance_deactivate_all(true);
if instance_exists(pause_sprite) sprite_delete(pause_sprite);
pause_sprite = sprite_create_from_surface(application_surface, 0, 0, room_width, room_height, false, false, 0, 0);
}else{
pause_ = false;
instance_activate_all();
}
之后能实现暂停功能,但是暂停画面只会用原客户端画面的左上角四分之一来覆盖
这是游戏画面
这是暂停画面
不知道该怎样去解决,希望能得到大佬们的耐心解答!十分感谢!!
只能使用 gml 编程吗?
如题,问下大家,有没有使用过别的语言在 gms 上编程,比如 lua、c#、js、python 之类的?
关于 lua 我有看到一个运行时编译到 gml 的库:https://marketplace.yoyogames.com/assets/5192/apollo-execute-lua-code
不过挺贵的大概 100 块。
--- 槽点 ---
gml 蛮好的,但是写着写着强迫症发作了。
不能在 object 的 event script 里面定义 function,而必须在 scripts 下 create 一个 script 再来调用,对我这种喜欢小函数的人来说简直灾难... 一个难管理,另一个不知道会不会影响启动性能。而且调用的时候都是全局的,不能通过 namespace 来隔离。
PS:
虽然官方的 roadmap 有提到 inline function,解决了可以在 event script 里面写 function 的问题:
- GML: Inline functions - allow var a = function( a, b ) { ….. }
但是目前还不能用...
顺便再问问,大家是怎么组织代码的,总感觉 gml 的代码组织方式很容易写乱,求老司机分享。谢谢~
20180615
- https://share.weiyun.com/5IcZYo1 汉化文件chinese2018年4月10日
- https://share.weiyun.com/57unGHD runtime-2.1.4.218
- https://share.weiyun.com/5BnkxAz GameMakerStudio-Installer-2.1.4.295
【GameMaker: Studio】instance_place和instance_position和一个小坑
instance_place和instance_position,无论名字还是功能,都是非常接近的两个函数,其作用都是检测给定的坐标值上是否有指定的对象存在,如果有,就返回该对象的唯一ID。一眼看上去,差别相当细微。
不过,坑往往就在非常细微的差别之中。
昨天,我就踩进这个坑里去了。
事情是这样的,昨天晚上弄demo时,我惊讶地发现明明在房间a可以正常触发的事件,在房间b就怎么都触发不了了。
触发事件的代码思路很简单,检测鼠标是否在事件触发对象的碰撞遮罩(collision mask)上,如果是,用变量x记录该对象的唯一ID,变量x默认为noone,所以当x != noone时,按下鼠标左键,就能触发事件,会弹出一段文字。
为了查错,我把变量x的值画出来一看,结果发现在房间b中无论我如何把鼠标对准目标对象,得到的都是-4(即noone的值),而在房间a中就能十分顺利地获取ID。正因为在房间a事件是可以正常触发的,我没有第一时间怀疑问题出在触发事件的代码本身上,而是怀疑是否自己漏掉了什么条件,或者哪里的代码干扰了事件的正常触发。
然后就开始各种查错……
最后发现,原来之所以触发不了这个事件,是因为需要房间至少有两个该事件在,才能正确触发,而房间b里只有一个该事件存在,因此无法触发,添加一个之后也能正常工作了……
想了想,我是通过在该对象的step里,用instance_place(mouse_x,mouse_y,该对象ID)来检测的,然后悲剧就发生了……
为了查明原因,我仔细看了一遍instance_place的官方描述:
With this function you can check a position for a collision with another instance or all instances of an object using the collision mask of the instance that runs the code for the check. When you use this you are effectively asking GameMaker Studio 2 to move the instance to the new position, check for a collision, move back and tell you if a collision was found or not. This will work for precise collisions, but only if both the instance and the object being checked for have precise collision masks selected otherwise only bounding box collisions are applied...
也就是说,instance_place这个函数的工作原理,和碰瓷差不多,是把一个实例移到指定的坐标点,去碰另一个实例,如果碰到了,就返回被碰瓷实例的唯一ID。所以我在房间b的做法,无异于让触发事件的对象自己去碰自己,自然碰不到了。
然而,因为instance_place函数本身并不需要你填入用哪个实例来碰瓷,所以我一开始就没注意到这个工作原理。实现此类事件的正确做法,是用instance_position,这个函数就不是基于用实例碰瓷来判断的了(当然也享受不了实例的碰撞体积了),事件立刻可以正常触发了。
instance_place,我记住你了,以后走夜路给我小心点……
Roguelike+换装?国产独立像素游戏《MetalMind》
《MeatlMind》是一款我们正在研发的,像素风格,融合了传统Roguelike与换装的独立游戏。游戏采用Unity3D进行开发,预计在九月至十月推出游戏的试玩版本。目前游戏部分详细内容一登录摩点网创意版:https://zhongchou.modian.com/item/13876.html?_mdsf=home_czpro_web&_mpos=h_czpro_web#comment
在《MetalMind》中,四大装备模块与独特的换装系统,将会创造出无限的配装可能。每个人都可以去打造属于自己的战斗风格。不过丰富的装备,不代表可以无限制的去叠加。想要变得更强,就要懂得取舍。如果你对这个游戏感兴趣的话,欢迎大家随时给我们提意见。有兴趣的可以点上面的链接去摩点网创意版看看我们游戏的详细内容。喜欢的话,别忘了给我们点个赞,点一下看好创意~谢谢大家
GMS2官方文档的翻译
由于我英语不太好,翻译的过程也是学习的过程,我会在翻译过程中插入一些自己的见解,难免有些错漏,所以欢迎大家在评论区留言,共同探讨。
以下是链接,我把翻译的文章存到了那里,密码是:123456
http://www.showdoc.cc/web/#/75108021544342?page_id=426676082922485