瓷片碰撞的制作

之前在HeartBeast的2D横版射击游戏中曾经演示过通过检测对象碰撞事件来制作墙体和地面,但是如果在一个场景中大量使用对象可能会影响游戏的性能,而使用瓷片贴图(tileset)则不会有这样的问题,并且瓷片的绘制也更方便,因此如果能使用瓷片的碰撞事件来实现障碍物的功能就更为理想了。
今天就先介绍一种这样的方法,也许有的朋友会觉得这篇教程有些绕,但这已经是目前我能找到的相对好理解的方法了,并且在这个方法的启发下应该可以拓展思路去设计其他方法,大家大可一试。


准备工作:首先需要你自己创建一个玩家的对象,这个步骤不在本篇中单独说明
首先我们需要创建一个专门用于绘制碰撞块的瓷片贴图,这个贴图只需要一个单元格即可,不过由于GMS2中的瓷片贴图素材必须在最左上角单元格留出一个透明的格子,因此我们需要在精灵中把宽度设置为你所需要的单元格的2倍,比如下图我们需要的单元格是64x64,那素材精灵就应该是128x64,以此类推左侧的64x64留空,右侧的64x64则是你最终用于绘制瓷片的内容,并且由于这个瓷片仅用于碰撞事件的检测,最后是不会显示的,因此画面也只是简单用两个矩形进行标记。

然后我们用这个精灵创建对应的瓷片贴图,名称都可以自由定义,但在右侧每一个瓷片的尺寸一定要根据我们的素材的单元格尺寸填好

然后我们在游戏场景中单独设置一个瓷片图层用于放置这套用于检测碰撞事件的瓷片贴图,选择新建的瓷片图层后在右侧选择刚刚创建瓷片贴图,然后在图层中根据需要绘制你需要检测碰撞的画面:

然后我们回去编辑玩家对象,在玩家对象的创建事件(Create)中添加如下代码:

baseSpeed = 10 ;

var layerID = layer_get_id("CollisionLayer") ;
tileID = layer_tilemap_get_id(layerID) ;

spr_bbox_left = sprite_get_bbox_left(sprite_index) - sprite_get_xoffset(sprite_index) ;
spr_bbox_right = sprite_get_bbox_right(sprite_index) - sprite_get_xoffset(sprite_index) ;
spr_bbox_top = sprite_get_bbox_top(sprite_index) - sprite_get_yoffset(sprite_index) ;
spr_bbox_bottom = sprite_get_bbox_bottom(sprite_index) - sprite_get_yoffset(sprite_index) ;

首先我们定义了一个"baseSpeed"变量,在后面我们将用这个值来作为基数控制玩家的移动速度。
然后定义了一个临时变量"layerID"用来存储刚刚创建的专门用于放置碰撞瓷片贴图的图层索引ID,这个ID是通过"layer_get_id()"这个方法获取的,在参数中填写那个图层的名称即可。
接着定义一个新的变量"tileID"用来存储对应图层中使用的瓷片贴图的索引ID,这个ID是用"layer_tilemap_get()"的方法获取的,参数中使用的就是刚才临时变量中存储的图层ID。
最后连续定义了四个变量"spr_bbox_left"、"spr_bbox_right"、"spr_bbox_top"、"spr_bbox_bottom",这四个变量将分别用于存储当前精灵在场景中碰撞遮罩的上下左右边的位置,进而可以使用这些坐标来检测是否与碰撞检测的瓷片相遇以触发相关事件。
而要明白这几个变量的赋值代码,首先需要知道"sprite_get_bbox_left()"和"sprite_get_xoffset()"这两个方法的含义(其余类似right、top、bottom和yoffset等不单独列出介绍了)。
"sprite_get_bbox_left()"这个方法是用于获取某个精灵的碰撞遮罩最左侧与精灵图像最左侧的距离的(right、top和bottom以此类推计算右侧、顶部和底部),使用这个方法可以计算出碰撞遮罩与精灵图像本身的相对位置。
"sprite_get_xoffset()"这个方法则是用来获取精灵的原点在精灵中的相对坐标(以精灵图像左上角为原点(0,0)的x轴坐标,yoffget则对应y轴坐标)。
在这两个方法中参数应当填入精灵的索引ID,而我们需要检测的是当前精灵,因此直接填入"sprite_index"这个自带的属性即可。
将碰撞遮罩上下左右的边坐标减去对应原点的相对坐标后,我们就可以得到精灵在场景中碰撞遮罩的四边与精灵原点的相对距离了,这部分可能有些绕,我在下面画了个大致的示意图,大家可以自行体会一下。

完成以上操作我们可以说现在做了好碰撞检测的准备工作,而真正检测碰撞事件我们还是要去"step"事件中继续编写:

然后我们分两个部分来看这段代码,首先是前面控制移动的部分

//声明控制横纵向移动用的临时变量
var xMove = 0 ;
var yMove = 0 ;

//检测用户的按键输入
if (keyboard_check(ord"A")) xMove -- ;
if (keyboard_check(ord"D")) xMove ++ ;
if (keyboard_check(ord"W")) yMove -- ;
if (keyboard_check(ord"S")) yMove ++ ;

//降低斜向移动速度
var moveSpeed = baseSpeed ;
if (xMove != 0 && yMove != 0)  moveSpeed = baseSpeed * 0.75 ;

首先定义了两个临时变量"xMove"和"yMove"用来控制横向和纵向的移动。
然后利用"keyboard_check"的方法监测用户是否按键,监测对象则是"W"、"A"、"S"、"D",这是除了上下左右方向键以外最常用来作为方向按键的四个按键了,而在参数中「ord"A"」这种写法则是GMS2中默认的用于获取字母数字等按键的键值的一个方法,一旦检测到用户按下对应的按键后,就递增/减"xMove"或"yMove"。
同时为了让移动看起来更自然,当用户同时按下两个按键以斜着运动时我们要适当降低移动速度,我们设置了一个临时变量"moveSpeed"来专门控制移动速度,默认将这个值设为"Create"事件中创建的"baseSpeed"的值,而当检测到当前对象同时在x轴和y轴都有移动时,则将"moveSpeed"设为"baseSpeed"的3/4。
下面来看控制碰撞检测部分的代码:

//水平方向的碰撞监测
x += xMove * moveSpeed ; //根据按键移动x坐标实现移动
if (xMove<0){ //即向左移动
    var t1 = tilemap_get_at_pixel(tileID,bbox_left,bbox_top) & tile_index_mask ;
    var t2 = tilemap_get_at_pixel(tileID,bbox_left,bbox_bottom) & tile_index_mask ;
    if (t1 != 0 || t2 != 0){
        x = ((bbox_left + 64) & ~63) - spr_bbox_left ;
    }
}
if (xMove>0){ //即向右移动
    var t1 = tilemap_get_at_pixel(tileID,bbox_right,bbox_top) & tile_index_mask ;
    var t2 = tilemap_get_at_pixel(tileID,bbox_right,bbox_bottom) & tile_index_mask ;
    if (t1 != 0 || t2 != 0){
       x =( (bbox_right  & ~63) -1)- spr_bbox_right ;
    }
}
//垂直方向的碰撞监测
y += yMove * moveSpeed ;//根据按键移动y坐标实现移动
if (yMove < 0){ //即向上移动
    var t1 = tilemap_get_at_pixel(tileID,bbox_left,bbox_top) & tile_index_mask ;
    var t2 = tilemap_get_at_pixel(tileID,bbox_right,bbox_top) & tile_index_mask ;
    if (t1 != 0 || t2 != 0){
        y = ((bbox_top +64)  & ~63) - spr_bbox_top     ;
    }
}
if (yMove > 0){ //即向下移动
    var t1 = tilemap_get_at_pixel(tileID,bbox_left,bbox_bottom) & tile_index_mask ;
    var t2 = tilemap_get_at_pixel(tileID,bbox_right,bbox_bottom) & tile_index_mask ;
    if (t1 != 0 || t2 != 0){
        y = ((bbox_bottom  & ~63)-1) - spr_bbox_bottom ;
    }
}

以上分别是检测玩家对象在上下左右的不同方向是否与最初制作的瓷片贴图碰撞的,一旦检测到对应的碰撞事件则根据运动的方向固定x或y轴坐标的值,以实现被阻挡的效果(在对应移动方向固定值则无法继续移动)。
最主要实用的是"tilemap_get_at_pixel()"这个方法,分别检测四个角所在的像素位置是否有"tileID"对应的瓷片存在,一旦有则认为发生碰撞从而固定x、y的坐标值。
最后用检测到碰撞发生的坐标根据运动方向减去之前计算得出的对应方向上碰撞遮罩与精灵原点的相对距离,就得到了精灵原点应当附着的对应x、y值。


其实写到最后的时候我自己都有一点绕的头晕,虽然明白这些操作的含义却不知道如何表达能让所有人都清楚,该方法取自Drift Gaming的教程,生肉视频链接我会放在阅读原文里,有需要的可以点过去配合来看。

2017-07-20 20:39
Comments
Write a Comment