GameMaker8.0 新手教程 Part 12 -碰撞(下)-

分类栏目:gamemaker教程

721

GameMaker8.0 新手教程 Part 12 -碰撞(下)-

7、碰撞检测函数

place_meeting(x,y,obj) :判断实例在(x,y)是否与指定对象的实例碰撞,返回值为1(真)或0(假)。参数obj也可以填实例id而不是对象名。
举例来说,在obj1里检测place_meeting(200,200,obj2)
GameMaker8.0 新手教程 Part 12 -碰撞(下)-
虽然(200,200)这个坐标并不在obj2的碰撞盒范围内,但是,如果把obj1的碰撞盒移动到(200,200)的位置,obj1的碰撞盒就会和obj2的碰撞盒重叠,那么place_meeting(200,200,obj2)就会返回1。
注意:obj1的碰撞盒并没有真正移动,只是在假设的前提下判断是否发生碰撞。
如果你想要检测是否与任何实例发生碰撞,你也可以在参数obj处填写all。
all是GM的特殊实例之一,它的id是-3,代表所有的实例。比如,你可以用all.x = 100;把所有实例都移动到x为100的位置来。
place_empty(x,y) :该函数用来检测,如果把实例移动到(x,y)位置,是否会与任何实例发生碰撞。注意函数名是empty,所以,不会发生碰撞就返回1,会发生碰撞则返回0,和place_meeting是相反的。
place_free(x,y) :函数同上,只是这个函数检测的是是否会与固体实例发生碰撞而不是全部实例。

有时候,我们不仅是想要判断是否发生碰撞,还想要知道是和哪个实例发生了碰撞。

instance_place(x,y,obj) :这个函数的原理和place_meeting是一样的,也是假设把实例的碰撞盒移动过去,检测是否发生碰撞。只不过,这个函数返回的是被碰撞的实例的id,如果不发生碰撞,则返回noone。
noone也是GM的特殊实例之一,id是-4,它的意思是“没有实例”。
复习一下我们学过的其他特殊实例:
global(-5),表示一个全局实例,它的变量不会因为房间变换而被销毁。
self(-1),表示实例自己,如果一个变量名被globalvar声明了,那么之后使用这个变量名一律表示全局变量,如果实例自身也有一个局部变量是同样的名字,就要用self.作为前缀。
other(-2),表示with()结构中调用with的实例,或者碰撞事件中被碰撞的实例。
all(-3),表示所有的实例。
按照id的顺序排列,就是self,other,all,noone,global。请不要觉得他们很牛逼,事实上,他们就是普通的整数罢了。比如说,a = other * global,得到a的值为10。你完全可以把这些特殊实例作为普通数据来进行运算。
其实,GM还有id为-6和-7的两个特殊实例,不过这两个是真的一点用都没有(所以官方文档也没提到这两个),所以本教程也不会讲解这两个特殊实例,有兴趣可以看看这个帖子了解一下:【太爱理论系列】细谈变量

collision_point(x,y,obj,prec,notme) :这个函数和之前的函数的区别在于,被检测的位置(x,y)必须在被检测的obj的实例的碰撞盒范围内才会判定为碰撞,也就是说,与执行函数的实例本身的碰撞盒点关系都没有。返回值依然是实例id或者noone,参数obj同样可以填all。
对于参数prec和notme,一般而言分别设置为1即可,几乎不会用到别的值。不想了解其中的内涵的话,可以直接跳到下一个函数了。但是如果你无论如何也想了解prec和notme这两个参数是什么意思,那就继续往下看。
首先,prec指的是precise collision checking,即精准碰撞检测,在第二节“精灵的碰撞盒”中我们讲到了它,不记得的可以回去翻一翻。如果被检测的实例的精灵关闭了精准碰撞检测,那么prec这个参数填写0和1是没有任何区别的。但如果被检测的实例的精灵开启了精准碰撞检测,prec如果填写1,则按照精灵实际设置的碰撞盒来检测,prec如果填写0,则会强行无视精灵所勾选的精准碰撞检测,而是把第二节中说过的最小矩形当做精灵的碰撞盒来检测。
然后,notme就是not me,非我。若notme填写0,且参数obj填写的是all或者实例自己的对象名,那么这个函数会把实例自己也作为检测对象,如果被检测位置(x,y)在实例自己的碰撞盒范围内,那么函数会返回实例自身的id。notme填写1时,就不会把自身作为碰撞检测目标了,与实例同一个对象的其他实例则还是会被检测到。
collision_rectangle(x1,y1,x2,y2,obj,prec,notme) :与上一个函数相同,执行函数的实例本身的碰撞盒并不会参与碰撞判定,但是取而代之的是,这个函数会以(x1,y1)和(x2,y2)作为对角线生成一个矩形,当被检测的obj的实例的碰撞盒与这个矩形重叠时,函数就会返回这个实例的id。
collision_circle(xc,yc,radius,obj,prec,notme) :这个函数是以(xc,yc)作为圆心,以radius作为半径生成一个圆,然后判断被检测的obj的实例的碰撞盒是否与这个圆重叠。
collision_ellipse(x1,y1,x2,y2,obj,prec,notme) :这个函数是以(x1,y1)和(x2,y2)作为对角生成一个椭圆来判断碰撞。注意,生成椭圆的方式如下图所示,只能生成正椭圆,不能生成斜椭圆。
GameMaker8.0 新手教程 Part 12 -碰撞(下)-
collision_line(x1,y1,x2,y2,obj,prec,notme) :这个函数是检测连接(x1,y1)和(x2,y2)的线段是否经过了被检测的obj的实例的碰撞盒。通常用来判断游戏中敌人能否看到玩家角色。

8、step事件模拟并取代碰撞事件

我们很久以前就提到过,最好用step事件去取代别的事件,因为这样可以随心地自己掌控各个事件的执行顺序。比如,在碰撞事件中,如果一个实例有两个碰撞事件,并且在同一步内都触发了,那么哪个碰撞事件先执行?猜测GM的执行顺序是一件痛苦的事,所以不如由自己来掌控执行顺序。
现在你可能还没有觉得有什么区别,但是等到下一章按键检测的时候,你就会理解这样做的好处。

对于普通的非固体碰撞,在步事件里if(place_meeting(x,y,obj){}就能达成和碰撞事件一样的效果了,如果要模拟other,可以用inst = instance_place(x,y,obj);if(inst){},通过inst来代替other。
所以重点就是如何模拟并取代固体的碰撞。首先,我们要知道,固体碰撞实际上有很多的问题,最明显的问题就是当速度比较大的时候,容易出现下面这种情况:
GameMaker8.0 新手教程 Part 12 -碰撞(下)-
objBall悬停在objBlock的上方。当参与固体碰撞的二者之间的距离小于实例的相对速度时,固体碰撞事件可不会自动帮你补全这段距离。

用step的解决思路是(注意,被碰撞方仍要勾选固体属性):
if(!place_free(x + hspeed, y))
{
if(hspeed <= 0)
move_contact_solid(180,abs(hspeed));
if(hspeed > 0)
move_contact_solid(0,abs(hspeed));
hspeed = 0;
}
if(!place_free(x, y + vspeed))
{
if(vspeed <= 0)
move_contact_solid(90,abs(vspeed));
if(vspeed > 0)
move_contact_solid(270,abs(vspeed));
vspeed = 0;
}
if(!place_free(x + hspeed, y + vspeed))
hspeed = 0;
讲解:

(x + hspeed, y)是预判实例下一帧是否会在水平方向碰撞到objBlock,(x, y + speed)则是预判垂直方向,(x + hspeed, y + vspeed)预判斜角方向。
move_contact_solid(dir, maxdist) :以速度maxdist向dir方向运动,碰撞到固体时会停止。这个函数的特点是,如果最后一步与固体的距离小于maxdist,会补全这一段距离。

效果如下:
GameMaker8.0 新手教程 Part 12 -碰撞(下)-
注意这里使用了!place_free()的判定,所以对一切固体实例都会生效,如果你只想对某个特定对象或实例生效,应该改用place_meeting(),但是这在很复杂的地形中可能会出现问题,所以不建议单独对特定对象或实例进行固体碰撞,而应该善用GM的固体属性,把不能穿透的物体都设置为固体。
值得注意的是,上述代码写在碰撞事件中也是有效的,如果你觉得麻烦不想用step,想用碰撞事件也可以的。