上一讲我们讲了为什么调用了cc.game.run后,会执行cc.game.onStart函数。这一讲,我们通过分析一下 CCDirector.js的代码,来了解一下游戏是怎么渲染的。
上一讲中,我们看到,在cc.game.prepare方法里面,在加载引擎代之后,执行了 cc.game._setAnimFrame()和cc.game._runMainLoop()。
接下来我们看下这两个函数到底做了什么。
// @Time ticker section
// 设置帧率
_setAnimFrame: function () {
// 预计下一帧执行后的时间
this._lastTime = new Date();
// 帧率
var frameRate = cc.game.config[cc.game.CONFIG_KEY.frameRate];
// 每帧的时间
this._frameTime = 1000 / frameRate;
// 如果帧率不是60或30的时候,使用window.setTimeout来控制执行频率
if (frameRate !== 60 && frameRate !== 30) {
window.requestAnimFrame = this._stTime;
window.cancelAnimationFrame = this._ctTime;
} else {
// 60或者30帧的时候,使用默认的,30时通过 skip 跳过一帧的方式
// 使用默认比setTimeout速度快
// 疑问 不是30帧的时候,为什么不使用跳过的方式来实现?
window.requestAnimFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
this._stTime;
window.cancelAnimationFrame = window.cancelAnimationFrame ||
window.cancelRequestAnimationFrame ||
window.msCancelRequestAnimationFrame ||
window.mozCancelRequestAnimationFrame ||
window.oCancelRequestAnimationFrame ||
window.webkitCancelRequestAnimationFrame ||
window.msCancelAnimationFrame ||
window.mozCancelAnimationFrame ||
window.webkitCancelAnimationFrame ||
window.oCancelAnimationFrame ||
this._ctTime;
}
},
// 定时函数
_stTime: function (callback) {
// 当前时间戳
var currTime = new Date().getTime();
// cc.game._fameTime 值为 1000 / 帧率 单位为毫秒
// cc.game._lastTime 值为上次预计 执行到这里的时间戳
// timeToCall下次执行callback间隔时间,正常时每帧的时间cc.game._frameTime
// currTime-cc.game._lastTime 就是预计和实际时间的差值
// 如果性能不好,currTime - cc.game._lastTime大于0,那下一帧的时间就要少一些
var timeToCall = Math.max(0, cc.game._frameTime - (currTime - cc.game._lastTime));
var id = window.setTimeout(function () {
callback();
},
timeToCall);
// 下一帧执行的预计时间,如果性能好的话,下次执行到_stTime函数时,这个值等你
cc.game._lastTime = currTime + timeToCall;
return id;
},
// 取消定时
_ctTime: function (id) {
window.clearTimeout(id);
},
通过上面的代码我们可以看到,如果帧率为60或30的时候,使用window.requestAnimationFrame的方式来定时执行函数。其他值时,使用window.setTimeout来实现。其中使用window.setTimeout的时候,通过记录预计下次执行的时间 cc.game._lastTime的值,来动态计算下次执行 回调的时间。
接下来看下
// 运行主循环
_runMainLoop: function () {
var self = this,
callback, config = self.config,
// projection.json的值
CONFIG_KEY = self.CONFIG_KEY,
// 导演
director = cc.director,
// 是否跳过渲染。帧率为30帧的时候 用
skip = true,
// 帧率
frameRate = config[CONFIG_KEY.frameRate];
// 设置FPS显示状态 为true的时候,左下角显示帧率等信息
director.setDisplayStats(config[CONFIG_KEY.showFPS]);
// 每帧回调的函数
callback = function () {
// 没有暂停的时候,继续执行
if (!self._paused) {
// 如果帧率是30
if (frameRate === 30) {
// 因为 帧率是30和60的时候,调用的是window.requestAnimationFrame,默认是60帧 (原因见cc.game._setAnimFrame方法)
// 所以每隔一帧跳过不执行
if (skip = !skip) {
self._intervalId = window.requestAnimFrame(callback);
return;
}
}
// 每帧执行导演类的 主循环
director.mainLoop();
self._intervalId = window.requestAnimFrame(callback);
}
};
// 间隔id,可通过该值来取消要执行的函数
self._intervalId = window.requestAnimFrame(callback);
// 是否暂停
self._paused = false;
},
可以看出,在cc.game._runMainLoop里面,通过调用window.requestAnimFrame来实现循环执行callback。在callback里面调用director.mainLoop(),来实现每一帧的更新。
下一讲,我们将通过分析CCDirector.js这个类来了解整个游戏渲染的流程