导言
这是一个初学者通过一个h5小游戏教程中出现的一些算法包括(对象池,脏矩形绘制,四叉树的2D碰撞检测)进行一个简单介绍,用于自我理解的目的。
游戏的DEMO,教程中的纵版改为横版。
教程中分为5个部分,包括从背景的制作到最后规则判定。其中关于玩家飞机和子弹判定这两个部分用到了对象池,脏矩形绘制,四叉树的2D碰撞检测算法。比较有意思。
对于游戏来说
对象池用于重复利用对象,这里是子弹,而不是对象创造,使用,消失,再创造的循环。脏矩形绘制用于判定子弹是否可以再利用。四叉树用来帮助子弹碰撞判定。
对象池 Object Pool
再介绍对象池之前,先想一下,在射击游戏中,通常会在短时间内,产生,运行,移除大量对象(子弹)。这通常需要大量的运算,可能会导致游戏的卡顿。原因是因为后台程序垃圾收集器(garbage collector)会让系统资源清除已用内存(used memory)。
简单介绍一下垃圾收集器,垃圾回收是一个自动资源分配程序,用于清除不再需要的已用内存空间。
比如下例:
for (int i = 1; i < 10; i++) {
System.out.println(i);
}
当这个for
loop 运行时,会产生变量i
,内存会保存变量i
的所有数据。当loop结束时,变量i
就会被清除。
这个例子当中,变量i
会有足够多的内存保存。但是当变量i
所需内存在巨量增加时,而可用内存又不够时,垃圾回收机制会清除其他内存来保证变量i
所需的内存。这会导致短时间内的卡顿。
因此,对象池的目的就是减少变量i
所需的内存,从而增加游戏流畅度。本质上就是对旧对象的重复利用,而不是不停的创造,删除。
假象一下,一盒纸牌(纸牌盒代表内存容量),每次需要一个新纸牌(新对象,新子弹),你抽出一张使用,然后扔到垃圾桶。最终,纸牌会用完,或者垃圾桶满了需要倒掉(垃圾收集)。
在一个对象池中,纸牌是空白的,当需要的时候,直接写上需要的信息,用完后擦干净,放回纸牌盒。这样就可以循环。
一个标准的对象池包含两个部分,初始化容器,以及函数(get
和animate
)。
初始化容器,就是对设定一个包含n个对象的数组,并且实例化为空值。
下图设定了最大值maxSize为参数,并把子弹bullet的参数设置空值。也就是说子弹已经被加载了,只是参数为0而已(x和y的位置,和速度)。
function ObjectPool(maxSize) {
var size = maxSize;
var ObjectPool = [];
this.init = function() { //实例化并空值每一个子弹
for (var i = 0; i < size; i++) {
var bullet = new Bullet();
bullet.init(//);
pool[i] = bullet;
}
};
函数部分由get
和animate
组成。
get
用于发射子弹。函数用于把检测最后一个对象是否正在被使用(alive or dead),如果没有被使用(dead),函数会初始化(这里指的是赋值子弹的默认参数)并把它排到第一位(发射)。如果正在被使用(通常情况下不会,不然也可以调整最大值),可以再创建一个对象。
this.get = function(x, y, speed) {
if(!pool[size - 1].alive) {
pool[size - 1].spawn(x, y, speed);
//赋予子弹的默认参数,也就是发射子弹
pool.unshift(pool.pop());
}
};
animate
重复利用已经被创造对象。检测对象是否被能被使用,如果为true,清除参数,并放置到容器中。其中检测对象的方式依据项目而异。
this.animate = function() {
for (var i = 0; i < size; i++) {
//通过alive和draw的返回值检测
if (pool[i].alive) {
if (pool[i].draw()) {
pool[i].clear();
pool.push((pool.splice(i,1))[0]);
}
}
else
break;
}
};
本例中,用了脏矩形技术(Dirty rectangles)检测。
首先alive
的判定方式是看对象是否被赋值使用。draw
用的是脏矩形技术检测。
脏矩形技术用于检测画面中变化的部分,这样就不需要每一帧重绘全部图像。这里就是判定目标对象。
this.draw = function() {
this.context.clearRect(this.x, this.y, this.width, this.height);
this.y -= this.speed;
//检测目标有没有移出屏幕
if (this.y <= 0 - this.height) {
return true;
}
else {
this.context.drawImage(imageRepository.bullet, this.x, this.y);
}
};
上例当中,draw
主要用于检测目标是否移动到屏幕外,如果为true,。
之前的animate
函数中的意义就是,检测子弹有没有飞出去,有没有飞到屏幕外,如果两个都为true,则把飞出去的子弹移到容器中。
四叉数 QuadTree
四叉树用于物体碰撞检测,当物体只有两个时,只需要直接计算就可以得出碰撞,当物体数量过多时,比如100个,那么计算碰撞压力就很大,因为每一个物体都要跟其余99个计算一次碰撞。
四叉树通过把屏幕划分4个区域,只有区域内或者区域线上的物体才会有可能发射碰撞。四叉树返回的是有可能发生碰撞的物体,只需要计算这些物体就可以。
上图包含了4个区域,其中坐上区域又被划分为4个子区域,因为对象数量过过多。
所以需要设定的是需要多少层区域划分,每一层所承载的最大对象。
回到游戏来说,四叉树检测可能过于小题大做,其他也有更方便的方法用于子弹检测。主要有利于理解四叉树原理。
引用:
JavaScript QuadTree Implementation
http://www.mikechambers.com/blog/2011/03/21/javascript-quadtree-implementation/
HTML5 Canvas Game: 2D Collision Detection
http://blog.sklambert.com/html5-canvas-game-2d-collision-detection/