根据鼠标点击自动选中对象

大家好,今天来介绍一下HeartBeast的一个很实用的技巧教程——如何在GMS2中实现鼠标点选画面中的对象之后自动选中该对象,如下GIF图所示:

首先我们需要创建对应的精灵文件,在这里为了区分选中和未选中的不同状态,我们给精灵设置两帧稍有区别的画面——纯蓝色块代表未选中,然后加一圈白色边框的作为选中状态

然后我们用这个精灵创建一个对象,在Create事件中声明两个属性:

selected = false ;
image_speed = 0 ; 


其中"selected"是我们的自定义变量,用这个变量来标识当前的对象是否被选中,默认为"false"未选中状态。
"image_speed"显示为绿色我们可以明白这是GMS2的内置属性变量,这是由于我们刚刚创建的精灵有两帧图像,这个速度是图像的播放速度,我们设置为0意思是这个精灵不需要自动切换自己的画面,固定在默认的画面上即可。
之后我们在该对象的"step"事件中再定义一个属性:

image_index = selected ;


同样我们看到又是绿色的内置属性"image_index",这个属性是用来设置当前的图像使用精灵中的第几帧图像的,我们的精灵只有两帧画面,对应的序号是"0"和"1",而刚刚定义的"selected"变量也只有选中和未选中两种状态,对应的"true"和"false"也恰好等于"1"和"0",因此这句代码就表示当前对象的画面根据自身是否被选中的状态自动切换画面,就能从视觉上给予用户选中和未选中的反馈效果了。
接下来我们就要设置如何去切换选中状态了,首先我们给对象创建一个鼠标的点击事件:

这里我们选择普通的左键点击事件,这意味着鼠标必须在对象范围内点击才会触发该事件,这个事件中我们先加一行简单的代码:

selected = true ;


即当鼠标在对象的画面范围内点击时,将自定义变量"selected"设置为"true",即当前对象被选中,根据之前的代码我们知道此时图像会自动切换到序号为"1"的第二帧带白色边框的画面上了,理论上似乎只要这样就完成了鼠标点击和自动选中的工作,但我们实际测试一下游戏会发现,这么做并不完善。

当我们的对象相互叠加时,如果鼠标点击的区域恰好是几个叠加对象的交集,那就会几个对象同时切换到选中状态,这很明显是违背游戏常识的,通常情况我们只会选中最靠近画面前方的对象,被覆盖在下方的对象是不会被选中的,因此我们需要再进行一些调整。

首先我们再创建一个新的对象,并且给这个新的对象添加一个全局的鼠标左键点击事件:

然后在这个事件中添加以下内容:

with (o_object){
    selected = false ;
}
var instance = instance_postion(mouse_x,mouse_y,o_object);
if instance_exists(instance) {
    instance.selected = true ;
}

首先,这是一个全局的鼠标左键单击事件,意味着在游戏画面中每一次鼠标点击都可以触发该事件,然后第一个"with(o_object)"代表这个事件需要联动修改所有"o_object"对象所生成的实力,大括号中的代码表示,修改的是这些对象的选中状态,全部修改为未选中状态。
然后我们用定义了一个临时变量"instance",并且用"instance_potion()"的方法来检测当前鼠标点击的区域内是否有"o_object"所生成的实例,如果有就把这个实例存到"instance"这个临时变量中。
最后在"if"语句中使用"instance_exists()"的方法判断刚刚的临时变量中是否已经存储了实例,如果已经有实例了就将实例的选中状态设置为选中状态。
这里由于临时变量被赋值只能赋一个实例,因此可以确保不会出现三个实例同时被选中的状态,但是当我们把这个对象放进我们的游戏场景中,然后测试游戏时还是会发现可能出现问题——游戏依然无法准确判断对象的深度,可能误选中靠后的实例。

这是因为在检测是否有实例并且进行赋值时是根据实例的创建顺序来判断的,而中离画面越深的实例其实越早被创建,因此在检测时优先检测到了靠后的对象

所以现在我们就知道我们该做什么了,我们需要去判断哪一个实例是比较靠前的,然后把最靠前的这个实例设置为选中状态即可,但是GMS2中并没有这种内置的方法可以实现这种判断,所以我们需要自己来写一个方法来实现这个功能,这需要我们新建一个"script"(脚本):

这个脚本我们命名为"top_instance_posion"即用来检测最靠上的实例的方法,然后其中的方法我先贴出全文并简单说明其中的思路,根据上次发的调查来看,目前还在看这个号的朋友大多都有一些学习编程的经验(只有一位朋友以前完全没有接触过编程),因此这段方法我暂时不具体解释每一行代码的意思,大家可以先自己尝试理解这个方法的使用,具体的解释我明天会放出:)。

///声明当前脚本的传入参数
///@param x
///@param y
///@param object
var xx = argument0 ;
var yy = argument1 ;
var object = argument2 ;

//创建一个实例列表
var instance_list = ds_list_create();

//检测第一个实例
var instance = instance_postion(xx , yy , object) ;

//创建一个最顶层实例的变量
var top_instance = instance ;

//循环检测每一个实例,检测深度属性
while instance_exists(instance){
    ds_list_add(instance_list,instance);
    instance_deactive_object(instance);
    if instance.depth < top_instance.depth{
        top_instance = instance ;
    }
    instance = instance_postion(xx,yy,object);
}

//重新激活所有的实例
while ds_list_size(instance_list )> 0{
    instance_active_object(instance_list[ | 0 ]);
    ds_list_delete(instance_list , 0);
}

//销毁列表
ds_list_destroy(instance_list);

//返回实例
return top_instance ;

以上,我简单给每一块功能做了简单的注释,下面是根据标记注释的区块来说明每个区块的具体功能

///声明当前脚本的传入参数
///@param x
///@param y
///@param object
var xx = argument0 ;
var yy = argument1 ;
var object = argument2 ;

第一部分如注释所说,是表明这个脚本允许被传入些什么参数的,这里定义了三个参数,分别是x、y和object
因为这个脚本是要用来检测识别鼠标点击位置最顶层的对象实例的,所以最后这个方法将被用来判断鼠标所在的坐标位置是否有某个对象的实例,基本上是"instance_position()"的一个加强版。

//创建一个实例列表
var instance_list = ds_list_create();

如果之前有看过翻译的"ds_list"相关内容,这里就能理解是建立了一个新的列表,这个列表在后面将用于保存检测到的实例ID,因为鼠标点击的位置可能有多个实例,因此用一个列表来保存所有检测到的实例。

//检测第一个实例
var instance = instance_postion(xx , yy , object) ;

这个方法其实昨天已经解释过了,就是GMS2内置的用来检测在某个坐标位置是否有某个特定对象的实例的方法,但是这个检测是按照实例的创建顺序来的,会默认返回最早创建的实例ID。

//创建一个最顶层实例的变量
var top_instance = instance ;

这里我们创建了一个临时变量"top_instance"来保存刚刚我们获取到的对应位置最早创建的那个实例ID

//循环检测每一个实例,检测深度属性
while instance_exists(instance){
    ds_list_add(instance_list,instance);
    instance_deactive_object(instance);
    if instance.depth < top_instance.depth{
        top_instance = instance ;
    }
    instance = instance_postion(xx,yy,object);
}

这一段可以说就是这个脚本文件最关键的地方,第一行是一个while循环语句,条件是鼠标点击位置是否获取到实例(如果没有有效实例则不继续触发后续内容)。
然后如果在对应位置确实有实例,首先把这个实例添加到刚刚创建的列表中保存。
然后把刚刚检测到的实例设置为未激活状态,在这种状态下之前的"instance_postion()"的方法就不再会检测到这个实例,在检测时会直接跳过去找之后创建的第一个实例。
然后我们拿检测到的实例的深度属性和目前"top_instance"中保存的实例的深度属性进行比较,一旦发现当前实例的深度属性较小,那就意味着这个实例更靠近顶层,于是就把"top_instance"中保存的实例ID替换成当前这个更靠近顶层的实例ID。
最后我们再次用"instance = instance_postion(xx,yy,object)"这个方法来检测当前位置是否还有有效实例存在,如果有就会把实例ID保存到"instance"这个变量中,这跟第一句while语句相配合就会循环触发这一整段的代码,如果没有就会跳过继续执行后面的代码。
这一整段代码的功能就是反复检测当前的位置是否有有效的实例,然后挨个检测保存并设为非激活状态,循环检测直到当前位置所有的实例都被检测一遍,并且从中找到深度属性最小即最靠近顶部的那个实例ID保存到临时变量"top_instance"中。

//重新激活所有的实例
while ds_list_size(instance_list )> 0{
    instance_active_object(instance_list[ | 0 ]);
    ds_list_delete(instance_list , 0);
}

由于之前那个循环在检测的过程中把所有的实例都变成了非激活状态,为了后续我们能继续正常使用这些对象因此需要把所有的实例重新激活,而我们之前每次检测到的实例都保存到了列表中。
因此我们又用了一个while循环,当列表中还有内容时,就把列表中第一个值对应的实例激活,然后把这个已经激活的值从列表中删除,反复操作直到列表中没有任何内容为止。

//销毁列表
ds_list_destroy(instance_list);

为了节约内存的使用,在列表已经使用完毕没有继续使用的加之以后我们从内存中销毁了这个列表。

//返回实例
return top_instance ;

最后我们把"top_instance"中保存的最靠近顶部的那个实例ID返回


可以看到,我们定义了一个新的方法,这个方法在定义完成之后,使用起来是跟GMS2中的那些内置函数一样的,我们只需要把原来那个检测鼠标点击位置的实例的方法改成我们自己定义的“检测所在位置最顶层实例”的这个方法即可。

OK,今天的脚本说明会到此结束,希望对大家有所帮助。

2017-07-14 19:13
Comments
Write a Comment