打灰机banner

最近微信掀起了一股“打灰机”热潮,不过你知道么,来财付通“打灰机”还能领大奖哦!还等什么,猛戳以下链接吧:http://action.tenpay.com/2013/airbattle/

想必大家都打完灰机了,现在想不想知道打灰机是怎么实现的呢?且容我抽丝剥茧,娓娓道来~~

游戏循环:

每一个游戏都是由获得用户输入,更新游戏状态,处理AI,播放音乐,还有画面显示这些行为组成。游戏主循环就是用来处理这个行为序列的。

所以首先我们最基本的就是创建一个游戏主循环。对于javascript来说,实现这个游戏循环只有setTimeout和setInterval 这两个方法可选了。

那到底是setTimeout好还是setInterval 好呢。其实毫无疑问的,setTimeout比较好。setTimeout是在设定的时间后执行一次指定方法,而setInterval是每隔一定的时间久执行指定方法。可能很多人会存在疑问,这样不是setInterval更好吗,直接就可以实现循环了,setTimeout还要用递归实现循环。没错,setInterval实现循环更简单,但是却不是更好。

因为:

  1. 无论是setTimeout还是setInterval,触发时,如果当前进程不为空,都得去排队等待执行,这一点上是无差异的。
  2. 区别是,setTimeout只需排一次队,setInterval则需要按照预设的间隔时间,每到时间点都去排一下。
  3. setInterval去排队时,如果发现自己还在队列中未执行,则会被drop掉。也就是说,同一个Interval,在队列里只会有一个。
  4. 因为队列机制,无论是setTimeout还是setInterval,第一次触发时的时间,只会等于大于预设时间,不可能小于。
  5. 对于setInterval来说,如果执行时间大于预设间隔时间,很可能导致连续执行,中间没有时间间隔,这是很糟糕的,很可能会耗费大量cpu。

所以对于动画来说,如果单帧的执行时间大于间隔时间,用setTimeout比用setInterval更保险。

于是利用setTimeout这样实现了游戏循环:

setTimeout(function(){ 
//循环体
 setTimeout(arguments.callee, 10); 
},10);

不过,真的这样简单吗?要知道javascript是单线程的,当要处理的事务比较多时,setTimeout的执行时间根本得不到保证,这样在不同性能的浏览器上就会有不同的表现了。这时我们可以利用时间差来控制循环体的执行时间。

var _last = new Date().getTime();

setTimeout(function(){

var _now = new Date().getTime();

if(_now - _last > delay){

_last = _now;

//循环体…

}

setTimeout(arguments.callee, 10);

},10);

这样,循环体执行的时间间隔就比较精准了。

游戏帧:
游戏循环有了,现在我们要明确的就是每个循环里要做些什么了。这里的每个循环就是我们所说的帧了。在我们的打灰机游戏里每一帧要做的事情无非就是下面这些 :

  1. 移动敌机
  2. 移动子弹
  3. 碰撞检测
  4. 游戏结束检测
  5. 补充敌机

移动敌机和子弹只要在当前的位置上加上当前的速度变量就可以了,比较简单。

我重点说说做碰撞检测。

碰撞检测:

打灰机游戏里的碰撞检测主要是检测子弹和敌机,敌机和玩家灰机的碰撞。在这里我们只做简单的矩形碰撞检测。在dom的世界全是方方块块的东东,至于飞机的形状,我想说 don't mind。要知道,我们是在用javascript做游戏,还得兼容该死的IE6,性能才是最重要的。忽略灰机的形状,这样碰撞检测就简单了,只要根据两个dom元素的位置和长宽判断是否有重叠就可以了。不过更简单的是,直接使用YUI里面的inRegion方法就可以了,哈哈。

既然如此简单,兴高采烈的开始代码了。开开心心的写个for循环,对每一架敌机和子弹做碰撞检测,然后对每一架敌机和玩家灰机做碰撞检测。大功告成,迫不及待的运行观看效果,然后小伙伴们都惊呆了!chrome下灰机机卡得一顿一顿的,而IE6直接罢工了有木有!我还是高估了javascript的性能,当务之急是对碰撞检测的性能做个优化。

性能优化:

每一屏内有十几架飞机,子弹和玩家灰机都分别和敌机做碰撞检测,则每一帧内要做上百次碰撞检测。如果只对可能发生碰撞的进行检测,每一帧的碰撞检测可以减少到十次以内。但是怎么知道哪些灰机是可能发出碰撞的呢。如果敌机可以出现在任意的位置上,那肯定是没办法做到的。所以只好把敌机固定在不同的航线上。如下图所示,

灰机航线

把游戏区域根据敌机的宽度划分成一条条固定的航线,敌机会随机出现在其中的一条航线上。于是,用子弹的x坐标除以敌机的宽度计算出子弹所处的航线,子弹只要和它所处的航线上的敌机作碰撞检测就可以了。

如果整个游戏区域分成10条航线,性能就提升了10倍!!同理,对于敌机和玩家灰机的检测也是如此处理。如此,性能提升20倍!!

操作优化:

实现玩家灰机的方向控制也是比较简单的,检测键盘事件的上下左右按键事件,对玩家灰机的x,y坐标做相应的增减即可。

但是在游戏中我们会发现玩家灰机的移动很不流畅,如果把移动速度增大,则会出现一跳一跳的感觉,如果移动速度变小玩家灰机的移动却很慢。这是因为键盘的事件响应频率太慢,所以只要提高移动控制的频率就可以解决这个问题了。类似上文所说的游戏循环,我们可以创建一个专门控制玩家灰机的移动的循环体,在循环体内不停检测标志位direction的值,根据direction的控制玩家灰机的移动方向。而在键盘响应事件中依据玩家按下的键对direction赋予不同的值,其中direction的值0表示静止,1表示向上移动,2表示向下移动,3表示向左移动,4表示向右移动。

实现代码如下:

键盘控制代码 QQ截图20131115124634

现在玩家灰机的移动控制就流畅很多了。

一款简单的打灰机游戏就这样炼成啦,最后我们再去打一把灰机吧。