从官方对creator引擎的产品定位中我们不难得知,cocos Creator是基于组件化脚本化开发,更具体点是基于js的脚本化,组件化开发
我们在组件脚本开发的时候首先注意的必定就是其生命周期了,目前creator提供给我们的生命周期回调函数主要有onLoad,start,update,lateUpdate,onDestroy,onEnable,
onDisable,我们可以简单描述一下这些回调触发的时机
onLoad回调函数会在节点首次激活时触发,在任何start函数调用前执行
start回调函数会在组件第一次激活前,也就是第一次执行 update 之前触发
update在组件进行更新时执行, 每一帧渲染前更新物体的行为
lateUpdate在所有组件的 update 都执行完之后
onDestroy当组件或者所在节点调用了 destroy()
onEnable当组件的 enabled 属性从 false 变为 true 时,或者所在节点的 active 属性从 false 变为 true 时
onDisable当组件的 enabled 属性从 true 变为 false 时,或者所在节点的 active 属性从 true 变为 false 时
需要注意的是我们的初始化通常在start函数而不再onload函数中的原因:onload是场景加载完成执行,start是当前组件开始运行时执行。假设每个组件的初始化都在onload完成,当一个节点的初始化依赖于另一个节点的属性时,就有可能出现异常,因为有可能另一个节点还没有执行onload。而start是所有组件的onload都执行完了才会执行,这时无论获取哪个节点的属性都不会有问题,因为那个节点已经初始化完成了。所以节点的UI初始化最好放在start中。
知道了这些,我们在做开发的时候就会有了一个大概的流程概念,在合适的时机处理自己想要的操作,晓得这些回调函数是在什么时候触发的了,天生丽质的宝宝们都会追根到底,那为啥你说他那个时候触发他就那个时候触发啊,ok,接下来,我们就详细的解析一
从cocos官方文档中我们可得知 Cocos Creator 的引擎部分包括 JavaScript、Cocos2d-x-lite 和 adapter 三个部分,引擎代码大致分为js和原生的c++ 两种类型,分别在引擎安装目录resources/engine 和 resources/cocos2d-x下。
咱们就从web平台来分析吧,主要看的就是engine\cocos2d 目录下的源码。
我们常说打出来的空包就要1-2兆,其实一部分原因就是需要把这些模块依赖给打包进去…em,有点跑题了
creator对应打出来的web包,对应的cocos2d-js-min.js文件所有模块的源码。整个文件的执行周期,main.js是creator引擎的入口,他会在cocos2d-js-min.js加载完之后开始运行游戏
如果简化main.js 的内容大概是这样的
//main.js
window.boot =function() {
//loading页面相关
function setLoadingDisplay (){...};
//主要是对Web和native游戏场景尺寸或者朝向的处理以及加载scene相关
var onStart = function(){
//以及调用上方的setLoadingDisplay 函数设置loading相关
};
//当前的游戏配置
var option = function(){...};
//s获取_CCSettings变量进行处理
cc.AssetLibrary.init({...});
//cc.game包含游戏主体信息并负责驱动游戏的游戏对象,传入配置参数
//运行游戏,并且指定引擎配置和 onStart 的回调
cc.game.run(option, onStart);
}
//调用
window.boot();
这里我们可以看到,如果简化来看,boot中就是声明和设置一些配置信息或者一些出来,以传参的形式传到run函数中
在run方法中,会根据配置进行引擎的准备工作,简单流程如下
//game
var game = {
//运行游戏,并且指定引擎配置和 onStart 的回调。
run(config, onStart){
// 注册配置数据
this._initConfig(config);
this.onStart = onStart;
// 开始准备
this.prepare(game.onStart && game.onStart.bind(game));
},
//准备引擎
prepare(cb){
//如果已准备则执行回调返回
if (this._prepared){
if (cb) cb();
return;
}
let jsList = this.config.jsList;
if (jsList && jsList.length > 0){
var self = this;
// 加载准备脚本之后调用
cc.loader.load(jsList, function (err) {
if (err) throw new Error(JSON.stringify(err));
self._prepareFinished(cb);
});
}else{
this._prepareFinished(cb);
}
},
prepareFinished(cb){
/*
//初始化引擎,设置帧循环事件等等方法
this._initEngine();
this._setAnimFrame();
.....
↑↑↑↑↑↑↑↑
至此已经完成了引擎的准备工作
*/
//开始运行主循环
this._runMainLoop();
//派发EVENT_GAME_INITED事件
this.emit(this.EVENT_GAME_INITED);
},
}
由上可知,运行的顺序在cocos2d-js-min.js加载完之后执行main.js的windows.boot,传入配置调cc.game.run函数,然后注册配置信息 开始准备prepare,加载脚本初始化引擎,设置帧事件,调用_runMainLoop开始运行主循环。
ok,到运行主循环这里就开始接触到我们要探究的生命周期回调函数了,
//Run game.
_runMainLoop: function () {
if (CC_EDITOR) {
return;
}
if (!this._prepared) return;
var self = this, callback, config = self.config,
director = cc.director,
skip = true, frameRate = config.frameRate;
// 设置在左下角是否显示fps信息
debug.setDisplayStats(config.showFPS);
callback = function (now) {
// 非暂停状态
if (!self._paused) {
//requestAnimationFrame是浏览器用于定时循环操作的一个接口,类似于setTimeout,主要用途是按帧对网页进行重绘
self._intervalId = window.requestAnimFrame(callback);
if (!CC_JSB && !CC_RUNTIME && frameRate === 30) {
// 每2帧跳1帧执行
if (skip = !skip) {
return;
}
}
// 执行下一帧循环
director.mainLoop(now);
}
};
self._intervalId = window.requestAnimFrame(callback);
self._paused = false;
},
可以看出,利用 window.requestAnimationFrame 及其回调函数来实现循环,循环里调用的是 director.mainLoop(),
//CCDirector.js
//在初始化时 有
sharedInit: function () {
this._compScheduler = new ComponentScheduler();
this._nodeActivator = new NodeActivator();
cc.loader.init(this);
},
mainLoop (now) {
if (this._purgeDirectorInNextLoop) {
this._purgeDirectorInNextLoop = false;
this.purgeDirector();
} else {
// 计算全局的时间增量,即 dt
this.calculateDeltaTime(now);
if (!this._paused) {
//每个帧的开始时所触发的事件
this.emit(cc.Director.EVENT_BEFORE_UPDATE);
// 对最新加入的组件调用 `start` 方法
this._compScheduler.startPhase();
// 调用组件的 `update` 方法
this._compScheduler.updatePhase(this._deltaTime);
//调用调度器的 `update` 方法
this._scheduler.update(this._deltaTime);
// 调用组件的 `lateUpdate` 方法
this._compScheduler.lateUpdatePhase(this._deltaTime);
// 将在引擎和组件 “update” 逻辑之后所触发的事件。
this.emit(cc.Director.EVENT_AFTER_UPDATE);
// 回收内存
Obj._deferredDestroy();
}
// 访问渲染场景树之前所触发的事件。
this.emit(cc.Director.EVENT_BEFORE_DRAW);
renderer.render(this._scene, this._deltaTime);
// 渲染过程之后所触发的事件
this.emit(cc.Director.EVENT_AFTER_DRAW);
eventManager.frameUpdateListeners();
this._totalFrames++;
}
},
ok,看到这里我们已经大概很明确这些回调了,接下来我们在深入的看一下ComponentScheduler类和NodeActivator类具体函数的实现,我们以start简化源码为例来看一下
//component-scheduler.js
function ctor(){
//开始
this.startInvoker = new OneOffInvoker(invokeStart);
this.updateInvoker = new ReusableInvoker(invokeUpdate);
this.lateUpdateInvoker = new ReusableInvoker(invokeLateUpdate);
//下一帧组件方法数组
this.scheduleInNextFrame = [];
// 一次循环中
this._updating = false;
},
var OneOffInvoker = cc.Class({
extends: LifeCycleInvoker,
//....
//....
invoke () {...}
)};
var invokeStar = function (iterator) {
var array = iterator.array;
for (iterator.i = 0; iterator.i < array.length; ++iterator.i) {
let comp = array[iterator.i];
//调用组件的 start 方法
comp.start();
comp._objFlags |= IsStartCalled;
}
},
startPhase () {
// 当前帧开始
this._updating = true;
if (this.scheduleInNextFrame.length > 0) {
this._deferredSchedule();
}
//调用 startInvoker
this.startInvoker.invoke();
},
我们可以看到在外部调用startPhase 方法后,他会去判断上个循环是否结束,然后调用this.startInvoke,而他在构造函数中已经实现。update,onenable等生命周期回调方法都在这个脚本中实现,而在NodeActivator类中的activateComp方法的工作就是调用并只调用一次组件的 onLoad 方法。这里就不在描述
好了,至此,已经分析了creator提供给我们的这些生命周期回调方法的实现。如有遗漏或不足,还请多多指教