魔,

2018-01-21 21:33

Canvas射击怪物游戏之getImageData()碰撞检测思路

本文作者:IMWeb 魔, 原文出处:IMWeb社区 未经同意,禁止转载

前段时间在制作射击怪物小游戏时(如图-1所示),在检测碰撞的算法上纠结了好久。正常的碰撞算法是通过计算元素的坐标值来进行碰撞判定。那么至少得有:怪物数量 x 子弹数量次的判断计算。考虑到游戏的拓展性,要求增加怪物与子弹数量的时候,那么计算量会增加得很可怕。于是乎我开始考虑有没有一种计算方式,只要循环判断每个怪物是否被子弹碰撞就好了,就这样,getImageData()函数引起了我的注意。
图示-1

使用getImageData()函数计算碰撞的思路是:
1.在Canvas绘制当前帧的时候,先绘制好子弹路径,然后将怪物的位置坐标以及宽高参数传递给getImageData()函数,即context.getImageData(locationX, locationY, width, height)(如图-2所示),这样就能获取到红色区域的像素数据。
2.由于画布的背景的是“空”的,所以如果没有其他像素(子弹元素)存在的话,获取的像素数据都是[0, 0, 0, 0],[0, 0, 0, 0],[0, 0, 0, 0]……,反之,如果数组中存在[0, 0, 0, 255](黑色不透明,子弹的像素颜色)的话,就说明怪物所在区域存在子弹元素,即两者碰撞。
3.根据碰撞检测的结果,调用drawImage()函数,绘制怪物相应的状态(存活或者死亡)。
如此以来,碰撞判定的计算量从 [怪物数量 x 子弹数量] 减少到 [怪物数量],算是大大减少了计算量。
图示-2

至此,我觉得这个想法非常完美,并且用javascript写进游戏里。结果运行游戏之后,画面什么鬼都没有,打开谷歌调试,发现了如下错误(如图-3所示)
图示-3
大概意思就是同源策略限制,getImageData()不能读取区域的像素信息。因为图片是本地文件,不存在域名,所以浏览器认为读取了外部资源,因而不允许读取外部图片信息。当然,如果将游戏放到本地服务器上调试是不会报错的。可我不想那么麻烦(懒),于是想有没有其他办法能够绕开或者避免该错误?

几经查询,发现真正出错的原因是因为调用了drawImage()函数之后,再调用getImageData()就会出错。说来也巧,在一本介绍Canvas使用的书籍中,提到了“画布状态”这个词。比如fillStyle,font等这些属性,设置过一次之后,往后相关的样式属性都会保持设置时的状态。然后灵光一闪,drawImage()函数肯定是要加载调用图片信息的,那么问题来了:

是否在画布里调用drawImage()之后,也改变了画布的某种状态呢?

顺着这个问题,那么解决思路就是重置画布状态,让它失忆,忘了之前调用过drawImage()函数,这样就可以使用getImageData()函数而不出错了。重点来了,经过测试:

通过重置画布宽高任一属性,canvas.width = canvas.width可以达到重置画布的效果。表面上看与调用clearRect()函数的效果是一样的,但内在的区别是:前者会完完全全的重置画布,也不保留相关“画布状态”。而后者只是清除区域像素,保留“画布状态”。

脚本的运行结果正如我预料的那样,在调用drawImage()函数之后,重置画布,再调用getImageData()是不会出错的。于是通过使用getImageData()函数的碰撞检测思路就成功了。

至此就结束了?不,还有一个问题,这样实现的碰撞检测运行内存消耗很大,原因就是getImageData()的区域越大,数据量就越大,因此最后还要改变一下getImageData()的获取区域。将原来获取的矩形区域,改成获取矩形的四条边,像素为1即可。原理就是只要子弹触碰到了边,即可视为碰撞。

结果这样的算法又带来了一个坑,那就是,如果某一帧子弹刚好绘制在空心矩形内部,岂不是捕捉不到了?因而,绘制子弹的时候,不仅仅要绘制子弹实体,还要绘制子弹的弹道。具体脚本就不展开了,思路是要绘制一条从上一帧子弹终点坐标,接连到当前帧子弹终点的透明线。这样无论子弹的速度多快,弹道这条线一定是会触发碰撞的。

0条评论

    您需要 注册 一个IMWeb账号或者 才能进行评论。