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

分类栏目:gamemaker教程

580

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

4、碰撞事件

碰撞事件(Collision Event),从字面上理解,就是判断两个对象的实例的碰撞盒是否重叠,如果重叠了,就会执行的一种事件。
新建碰撞事件,需要为这个事件选定一个碰撞对象,如图:
GameMaker8.0 新手教程 Part 12 -碰撞(中)-
一个对象可以为他设置多个碰撞事件,理论上,最多可以为一个对象设置等同于总对象数量那么多的碰撞事件(也可以设置和自己碰撞的碰撞事件)。
注意:只要二者的碰撞盒仍处于重叠状态,那么碰撞事件每一步都会执行一次。在一些碰撞判定中,比如子弹打中BOSS,如果没有任何特殊处理,那么子弹穿过BOSS的这几步,BOSS每一步都会减一滴血,所以,要么让子弹完成碰撞后立马instance_destroy(),要么给BOSS设置无敌时间。至于如何给BOSS设置无敌时间,后期我大概会写一个BOSS专题来讲。

正如之前所说,无论是中弹判定,接触判定,阻碍判定,其本质都是碰撞判定,比如,玩家角色与地面碰撞,就应该把速度清空,子弹与BOSS碰撞,就应该令BOSS扣血(用变量控制),玩家角色中弹(或触碰其他危险物品),就要先销毁玩家角色,然后再播放死亡音效,增加死亡数(最好作为全局变量),并且弹出死亡菜单或者其他死亡效果等(一般而言就是用大量的objects堆效果)。

5、other

在碰撞事件中,如果我们要让被碰撞方(即为碰撞事件选定的对象)做点事,如果被碰撞方的对象只有一个实例,我们仍然可以用传统的“对象名.xx”或者“with(对象)”方式。
但是如果被碰撞方同时有多个实例在场,而我们只要碰撞盒和碰撞方重叠的那个实例执行些什么,而不影响其他没有碰撞的实例,就应该用到GM提供的other关键字。
比如,在BOSS的对象中,新建一个碰撞子弹的事件,在碰撞事件中,我们应该让BOSS减血,假设用变量hp来储存血量,就应该是:

if(hp > 0)
hp -= 1;

但是正如我们上面所说,子弹穿过BOSS也是要时间的,在这段时间里,BOSS每一步都会减血,所以我们要摧毁和BOSS碰撞的子弹,但是又不能影响其他没有打中BOSS的子弹:

if(hp > 0)
hp -= 1;
with(other)
instance_destroy();

other就能胜任这一功能。

这并不是第一次提到other,在之前我们也有讲过,在with结构中,用other表示调用with的对象的实例:

xx = 55;
with(objHat)
xx = other.xx;


同样的,在碰撞事件中,你可以有这样的操作:

with(other)
xx = other.xx;

要分清楚这两个other的区别,with(other)的other指被碰撞的实例,other.xx的other则指执行碰撞事件的实例。

和global一样,other也是一种特殊实例,它指向的真实实例随着场合的不同而不同。other的索引是-2,即可以用(-2)来取代other关键字:

with(-2)
xx = (-2).xx;

6、固体属性

在第三章讲对象的时候,我们跳过了一些属性没有详讲,其中一个就是固体(Solid)属性。
GameMaker8.0 新手教程 Part 12 -碰撞(中)-
固体属性并非固定不变,你可以在代码中用solid = 0或solid = 1来改变一个实例的固体属性。

浏览GML的汉化文档,你会发现GML有很多对固体生效的函数,如下:
GameMaker8.0 新手教程 Part 12 -碰撞(中)-
很多函数都拆分为,对固体实例生效,和对所有实例生效两个函数。

这种分类,就更好地去模拟一些物理行为。(在后期讲高级运动函数时会详细解释)

但是固体属性的意义不止于此。
固体属性在碰撞事件中有十分重要的意义。

如图,我们新建一个objBall和objBlock,给objBall一个碰撞objBlock事件,在二者都不勾选固体的情况下:
GameMaker8.0 新手教程 Part 12 -碰撞(中)-
给objBall一个初速度:
GameMaker8.0 新手教程 Part 12 -碰撞(中)-
在房间里摆上二者:
GameMaker8.0 新手教程 Part 12 -碰撞(中)-
运行游戏,objBall会直接穿过objBlock,如下gif所示:
GameMaker8.0 新手教程 Part 12 -碰撞(中)-
但是如果我们给被碰撞体,即objBlock勾选上固体属性,其他仍然保持不变:
GameMaker8.0 新手教程 Part 12 -碰撞(中)-
objBall并不会穿过objBlock:
GameMaker8.0 新手教程 Part 12 -碰撞(中)-
这就是固体属性在碰撞中的意义:
如果被碰撞方是固体,那么碰撞方不会穿过被碰撞方,无论碰撞方是不是固体。
反过来也可以得出结论:
如果碰撞方是固体,那么碰撞方不会穿过被碰撞方,无论被碰撞方是不是固体。
总结成一句话:

只要参与碰撞的双方有一方或两方是固体,则不会发生穿透。

注意:固体属性是建立在有相应碰撞事件基础上的,如果二者并没有对应的碰撞事件,那么不管是不是固体都会穿透。
这个性质特别适合用来做横版游戏:把objBall想象成玩家角色,把objBlock想象成砖块,给砖块勾选固体属性,并给玩家角色添加碰撞砖块的碰撞事件,那么玩家从空中下落到砖块上时,就会自动停止在砖块上。

注意:与固体objBlock碰撞后,虽然objBall停止了,但这并不意味着速度被清除了,如果objBlock被移除了,objBall还会继续下落。
我们来理解一下发生了什么。首先,objBall自由下落,直到他的碰撞盒和objBlock的碰撞盒重叠了,触发objBall的碰撞事件,而碰撞事件在执行代码之前,还要先检测一下被碰撞的实例是不是固体。
这时碰撞事件发现发现objBlock是固体,那么在执行碰撞事件里的代码之前,碰撞事件还要再做一件额外的事情:让objBall退回到上一步所在的位置,即还没有发生碰撞的位置。(如果objBlock也在运动,objBlock也会回退到上一步所在的位置,虽然objBlock并没有碰撞事件,但是objBall的碰撞事件会承担这个职责)
回退到上一步的位置有什么用呢?
实际上,虽然看起来objBall是静止的,但由于速度并没有清零,每一步它的速度仍然实现了,每一步objBall都要往下移,然后碰撞盒与objBlock重叠,触发碰撞事件,又再一次地回到上一帧还没有碰撞的位置,以此保持动态平衡。
由于speed实现导致objBall下移,和objBall发生碰撞事件又回到原位,这两件事是在同一步之内发生的,而图像每一步只画一次,最终objBall的图像画在什么位置,取决于objBall最后在什么位置,也就是说,objBall下移是不会被表现出来的,所以我们看到的objBall是静止在objBlock上的,而不是上下鬼畜。
正因此,objBall停止在objBlock上时,会一直不断地执行碰撞事件和里面的代码,切勿以为碰撞事件只执行一次。
一般来讲,如果没有特殊需求,最好在固体碰撞事件中手动添加speed = 0;,即让碰撞事件只执行一次,这样其实在很多时候都更方便做游戏。