正文
今天这一部分主要讲游戏的实现原理与游戏循环的代码实现。
先说原理,大家都看过动画吧。在我看来,游戏就是玩家能人为控制动画剧情发展方向的动画。所以,我们的游戏引擎其实说白了就是个动画引擎再加上鼠标事件、键盘事件等这些能告诉动画接下来剧情走向的模块。你点一下鼠标,动画下一个画面就开枪了,你按一下方向键,动画人物下一个画面就朝着那个方向走一步,就这么简单。
具体到代码,我大概列出了游戏运行需要的主要模块以及他们在源代码的位置。其中最核心的模块就是中间的动画循环模块。他就像一列火车,每个模块就是一个火车站。当火车停靠了所有站台跑完一圈时游戏画面就进入下一帧,除非游戏暂停,动画循环会一直跑下去。
火车跑一圈叫做一帧。火车一秒钟跑过的圈数叫做帧数率(fps)。
所以我们首先要做的就是找到一个方法能让这个循环一直进行下去。我们知道window.setInterval()可以传入一个函数与一个毫秒为单位的时间间隔。每当到了间隔时间就会执行传入的函数。但这里我们不用他,而用HTML5标准定义的window.requestAnimationFrame()方法,这个方法接受一个函数作为参数,浏览器将根据绘制的最佳时机调用这个函数。 在这里比较下二者的优劣。
window.setInterval() window.requestAnimationFrame()
接受参数: 回调函数,间隔时间 | 回调函数
用处: 通用方法 | 专门处理动画,会对动画循环机制做优化
时间间隔: 手动设置,虽然以毫秒为单位但达不到毫秒级精确度 | 浏览器自动根据绘制的最佳时机设置
接下来,就是我们游戏循环的实现。 代码见game-engine 663行起
Game.prototype= { // 调用start方法会根据浏览器最佳时机调用animate方法 start: function () { var me=this; //保存this值,因为requestAnimationFrame()方法是window上的,直接在其中使用this会指向window而不是game的实例 this.startTime=getTimeNow(); //设置游戏开始时间 window.requestAnimationFrame(function (time) { //开始循环 me.animate.call(me,time); }); }, animate: function (time) { //这就是循环要执行的函数 var me=this; if (this.paused) { //如果游戏暂停了,不会执行下面的操作 setTimeout(function () { me.animate.call(me,time); },this.PAUSE_TIMEOUT); } else { //如果游戏没有暂停,依次执行下面的方法 this.tick(time); //用于更新游戏时间 this.clearScreen(); //清空游戏画面。 因为每一帧都会重绘游戏,所有的内容都会在新的位置绘制,所以得把上一帧的内容清除了 this.startAnimate(time); //当开始动画时运行的函数 this.paintUnderSprites(); //在精灵对象(也就是玩家和怪物)下绘制的内容 this.updateSprites(time); //更新精灵对象的行为 this.paintSprites(); //绘制精灵对象的新位置 this.paintOverSprites(); //精灵对象之后运行的函数 this.endAnimate(); //一帧动画结束时要运行的函数 window.requestAnimationFrame(function (time) { me.animate.call(me,time); }); } },//后续代码省略
有了这个循环,我们的游戏框架就大体搭起来了,接下来只要把每一帧动画都需要发生的事情写入animate方法中相应的部分,浏览器就会自动帮我们运行了。
今天就到这里吧,下一篇将介绍游戏运行需要的基本模块。