更舒适的阅读体验,请访问公众号:
官方文档:
生命周期回调:
https://docs.cocos.com/creator/manual/zh/scripting/life-cycle-callbacks.html
测试脚本
const { ccclass, property } = cc._decorator;
@ccclass
export default class LifeCycle extends cc.Component {
// LIFE-CYCLE CALLBACKS:
__preload() {
debugger
}
onLoad() {
debugger
}
onEnable() {
debugger
}
start() {
debugger
}
update(dt) {
// debugger
}
lateUpdate(dt) {
// debugger
}
onDisable() {
debugger
}
onDestroy() {
debugger
}
clickEvent() {
// this.node.active = false;
// this.node.active = true;
// this.node.destroy();
this.destroy();
}
}
触发顺序
一个组件从初始化到激活,再到销毁的生命周期函数调用顺序为:
接下来我们按生命周期触发的先后顺序看一下,每个回调函数的调用时机
要特别区分一下,生命周期是依赖 节点 还是依赖 组件
__preload、onLoad 依赖 节点
onEnable、start、update、lateUpdate、onDisable、onDestroy 依赖 组件
它们的区别在于:
如果脚本组件所在节点的 active = true,但脚本组件的 enabled = false,依然会执行 节点 相关回调,但不会执行 组件 相关回调,只有在 enabled = true 的情况下,组件 相关回调才会被执行
如果脚本组件所在节点的 active = false,无论脚本组件的 enabled 为 true 或 false,都不会执行任何回调
调用时机
1__preload onLoad onEnable
· __preload
官方文档没有说明,以下仅是个人理解
见名知意,其功能和 onLoad 相同,仅仅是调用时机在 onLoad 之前
· onLoad
脚本组件的初始化阶段,该回调会在节点首次激活时触发,比如所在的场景被载入,或者所在节点被激活的情况下
在 onLoad 阶段,保证了你可以获取到场景中的其他节点,以及节点关联的资源数据
该回调总是会在 start 方法调用前执行,这能用于安排脚本的初始化顺序
· onEnable
当组件的 enabled 属性从 false 变为 true 时,或者所在节点的 active 属性从 false 变为 true 时,会激活 onEnable 回调
倘若节点第一次被创建且 enabled 为 true,则会在 onLoad 之后,start 之前被调用
调用时机:
从以上调用时机可以推断,如果一个节点挂载了多个脚本组件,那么执行顺序是:
所有脚本组件的 onLoad → 所有脚本组件的 onEnable → 所有脚本组件的 start 等等
· 当节点激活后,即 active = true:
相关源码路径:
resources\engine\cocos2d\core\node-activator.js
resources\engine\cocos2d\core\component-scheduler.js
① active:访问器属性
当前节点的自身激活状态
值得注意的是,一个节点的父节点如果不被激活,那么即使它自身设为激活,它仍然无法激活
active: {
get () {
return this._active;
},
set (value) {
value = !!value;
if (this._active !== value) {
this._active = value;
var parent = this._parent;
if (parent) {
// 父节点的激活状态
var couldActiveInScene = parent._activeInHierarchy;
if (couldActiveInScene) {
// 更改节点激活状态
cc.director._nodeActivator.activateNode(this, value);
}
}
}
}
}
② activateNode:更改节点激活状态
activateNode (node, active) {
// 激活
if (active) {
var task = activateTasksPool.get();
this._activatingStack.push(task);
// 递归激活所有子节点及节点上的所有组件
this._activateNodeRecursively(node, task.preload, task.onLoad, task.onEnable);
// 调用 __preload()
task.preload.invoke();
// 调用 onLoad()
task.onLoad.invoke();
// 调用 onEnable()
task.onEnable.invoke();
this._activatingStack.pop();
activateTasksPool.put(task);
}
// 禁用
else {
// 递归禁用所有子节点及节点上的所有组件
this._deactivateNodeRecursively(node);
// remove children of this node from previous activating tasks to debounce
// (this is an inefficient operation but it ensures general case could be implemented in a efficient way)
var stack = this._activatingStack;
for (var i = 0; i < stack.length; i++) {
var lastTask = stack[i];
lastTask.preload.cancelInactive(IsPreloadStarted);
lastTask.onLoad.cancelInactive(IsOnLoadStarted);
lastTask.onEnable.cancelInactive();
}
}
node.emit('active-in-hierarchy-changed', node);
}
其中 task.preload,task.onLoad,task.onEnable
分别对应 _activateNodeRecursively 中的 preloadInvoker,onLoadInvoker,onEnableInvoker
另外关于生命周期中的几个调用者方法,由于篇幅所限,就不再啰嗦了,感兴趣的可以自己查阅
UnsortedInvoker
OneOffInvoker
ReusableInvoker
invokePreload
invokeOnLoad
invokeOnEnable
invokeStart
invokeUpdate
invokeLateUpdate
③ _activateNodeRecursively:递归激活所有子节点及节点上的所有组件
_activateNodeRecursively (node, preloadInvoker, onLoadInvoker, onEnableInvoker) {
// 如果节点正在反激活的过程中则返回
if (node._objFlags & Deactivating) {
// 对相同节点而言,无法撤销反激活,防止反激活 - 激活 - 反激活的死循环发生。
// 这样设计简化了一些引擎的实现,而且对调用者来说能保证反激活操作都能成功。
cc.errorID(3816, node.name);
return;
}
node._activeInHierarchy = true;
// component maybe added during onEnable, and the onEnable of new component is already called
// so we should record the origin length
var originCount = node._components.length;
// 激活该节点所有组件
for (let i = 0; i < originCount; ++i) {
let component = node._components[i];
if (component instanceof cc.Component) {
this.activateComp(component, preloadInvoker, onLoadInvoker, onEnableInvoker);
} else {
_componentCorrupted(node, component, i);
--i;
--originCount;
}
}
node._childArrivalOrder = node._children.length;
// activate children recursively
for (let i = 0, len = node._children.length; i < len; ++i) {
let child = node._children[i];
// 根据子节点排列顺序设置 _localZOrder
child._localZOrder = (child._localZOrder & 0xffff0000) | (i + 1);
if (child._active) {
// 递归调用
this._activateNodeRecursively(child, preloadInvoker, onLoadInvoker, onEnableInvoker);
}
}
node._onPostActivated(true);
}
_localZOrder 和 zIndex 的关系为:
zIndex = _localZOrder >> 16
_localZOrder = (_localZOrder & 0x0000ffff) | (value << 16)
④ activateComp:激活组件,填充对应的生命周期回调
activateComp: function (comp, preloadInvoker, onLoadInvoker, onEnableInvoker) {
if (!cc.isValid(comp, true)) {
// destroyed before activating
return;
}
if (!(comp._objFlags & IsPreloadStarted)) {
comp._objFlags |= IsPreloadStarted;
if (comp.__preload) {
if (preloadInvoker) {
preloadInvoker.add(comp);
} else {
comp.__preload();
}
}
}
if (!(comp._objFlags & IsOnLoadStarted)) {
comp._objFlags |= IsOnLoadStarted;
if (comp.onLoad) {
if (onLoadInvoker) {
onLoadInvoker.add(comp);
} else {
comp.onLoad();
comp._objFlags |= IsOnLoadCalled;
}
} else {
comp._objFlags |= IsOnLoadCalled;
}
}
if (comp._enabled) {
var deactivatedOnLoading = !comp.node._activeInHierarchy;
if (deactivatedOnLoading) {
return;
}
// 启用组件
cc.director._compScheduler.enableComp(comp, onEnableInvoker);
}
}
· 当组件启用后,即 enable = true:
相关源码路径:
resources\engine\cocos2d\core\components\CCComponent.js
① enabled:访问器属性
表示该组件自身是否启用
enabled: {
get () {
return this._enabled;
},
set (value) {
if (this._enabled !== value) {
this._enabled = value;
if (this.node._activeInHierarchy) {
var compScheduler = cc.director._compScheduler;
if (value) {
// 启动该组件
compScheduler.enableComp(this);
} else {
// 禁用该组件
compScheduler.disableComp(this);
}
}
}
},
visible: false,
animatable: true
}
② enableComp:启用组件,填充对应的生命周期回调
enableComp: function (comp, invoker) {
if (!(comp._objFlags & IsOnEnableCalled)) {
if (comp.onEnable) {
if (invoker) {
invoker.add(comp);
return;
} else {
comp.onEnable();
var deactivatedDuringOnEnable = !comp.node._activeInHierarchy;
if (deactivatedDuringOnEnable) {
return;
}
}
}
this._onEnabled(comp);
}
}
③ _onEnabled:启用组件
_onEnabled (comp) {
cc.director.getScheduler().resumeTarget(comp);
comp._objFlags |= IsOnEnableCalled;
// schedule
if (this._updating) {
// 如果在当前帧内激活该组件,则会被放入到延时队列中
this._deferredComps.push(comp);
} else {
this._scheduleImmediate(comp);
}
如果是在当前帧内激活组件,就会把该组件放入到延时队列中,等到当前帧结束时(lateUpdatePhase)触发 start 回调,然后在下一帧触发 update 和 lateUpdate
lateUpdatePhase:
lateUpdatePhase (dt) {
this.lateUpdateInvoker.invoke(dt);
// End of this frame
this._updating = false;
this._startForNewComps();
}
其中 __preload 、onLoad 通过对标志位(_objFlags)进行位运算来判断是否填充过对应的回调,以保证生命周期内只调用一次
if (!(comp._objFlags & IsPreloadStarted)) {
comp._objFlags |= IsPreloadStarted;
xxx
}
if (!(comp._objFlags & IsOnLoadStarted)) {
comp._objFlags |= IsOnLoadStarted;
xxx
}
而 onEnable 是通过判断该组件是否启用(_enabled),所以该组件每次启用后都会被调用一次
if (comp._enabled) {
xxx
}
· 利用位运算设置标志位
① 设置标志位
comp._objFlags |= IsOnEnableCalled;
② 关闭标志位
comp._objFlags &= ~IsOnEnableCalled;
③ 判断标志位
if (comp._objFlags & IsOnEnableCalled)
3start update lateUpdate
· start
start 回调函数会在组件第一次激活后,也就是第一次执行 update 之前触发
· update
游戏开发的一个关键点是在每一帧渲染前更新物体的行为,状态和方位。这些更新操作通常都放在 update 回调中
· lateUpdate
update 会在所有动画更新前执行,但如果我们要在动效(如动画、粒子、物理等)更新之后才进行一些额外操作,或者希望在所有组件的 update 都执行完之后才进行其它操作,那就需要用到 lateUpdate 回调
调用时机:
相关源码路径:
resources/engine/cocos2d/core/CCDirector.js
mainLoop:
Run main loop of director
mainLoop: function (now) {
if (this._purgeDirectorInNextLoop) {
this._purgeDirectorInNextLoop = false;
this.purgeDirector();
} else {
// calculate "global" dt
this.calculateDeltaTime(now);
// Update
if (!this._paused) {
// before update
this.emit(cc.Director.EVENT_BEFORE_UPDATE);
// Call start for new added components
this._compScheduler.startPhase();
// Update for components
this._compScheduler.updatePhase(this._deltaTime);
// Engine update with scheduler
this._scheduler.update(this._deltaTime);
// Late update for components
this._compScheduler.lateUpdatePhase(this._deltaTime);
// User can use this event to do things after update
this.emit(cc.Director.EVENT_AFTER_UPDATE);
// Destroy entities that have been removed recently
Obj._deferredDestroy();
}
// Render
this.emit(cc.Director.EVENT_BEFORE_DRAW);
renderer.render(this._scene, this._deltaTime);
// After draw
this.emit(cc.Director.EVENT_AFTER_DRAW);
eventManager.frameUpdateListeners();
this._totalFrames++;
}
}
4onDisable onDestroy
· onDisable
当组件的 enabled 属性从 true 变为 false 时,或者所在节点的 active 属性从 true 变为 false 时,会激活 onDisable 回调
· onDestroy
当组件或者所在节点调用了 destroy(),则会调用 onDestroy 回调,并在当前帧结束时统一回收组件
· 当节点禁用后,即 active = false:
相关源码路径:
resources\engine\cocos2d\core\node-activator.js
① _deactivateNodeRecursively:递归禁用所有子节点及节点上的所有组件
_deactivateNodeRecursively (node) {
// 设置标志位
node._objFlags |= Deactivating;
node._activeInHierarchy = false;
// 禁用该节点所有组件
var originCount = node._components.length;
for (let c = 0; c < originCount; ++c) {
let component = node._components[c];
if (component._enabled) {
cc.director._compScheduler.disableComp(component);
if (node._activeInHierarchy) {
// reactivated from root
node._objFlags &= ~Deactivating;
return;
}
}
}
//递归调用
for (let i = 0, len = node._children.length; i < len; ++i) {
let child = node._children[i];
if (child._activeInHierarchy) {
this._deactivateNodeRecursively(child);
if (node._activeInHierarchy) {
// reactivated from root
node._objFlags &= ~Deactivating;
return;
}
}
}
node._onPostActivated(false);
node._objFlags &= ~Deactivating;
}
· 当组件禁用后,即 enable = false:
相关源码路径:
resources\engine\cocos2d\core\component-scheduler.js
① disableComp:禁用组件
disableComp: : function (comp) {
if (comp._objFlags & IsOnEnableCalled) {
if (comp.onDisable) {
comp.onDisable();
}
this._onDisabled(comp);
}
}
· 当节点销毁后,即 node.destroy():
相关源码路径:
resources\engine\cocos2d\core\utils\base-node.js
destroy:销毁节点
destroy () {
if (cc.Object.prototype.destroy.call(this)) {
this.active = false;
}
}
① cc.Object.prototype.destroy.call(this):
销毁该对象,并释放所有它对其它对象的引用
实际销毁操作会延迟到当前帧渲染前执行,从下一帧开始,该对象将不再可用
您可以在访问对象之前使用 `cc.isValid(obj)` 来检查对象是否已被销毁
相关源码路径:
resources\engine\cocos2d\core\platform\CCObject.js
destroy:
prototype.destroy = function () {
if (this._objFlags & Destroyed) {
cc.warnID(5000);
return false;
}
if (this._objFlags & ToDestroy) {
return false;
}
// 设置标志位
this._objFlags |= ToDestroy;
// 放入待销毁队列,在当帧结束时统一回收组件
objectsToDestroy.push(this);
return true;
};
② this.active = false
同禁用节点
· 当组件销毁后,即 comp.destroy()
destroy:组件销毁
destroy () {
if (CC_EDITOR) {
var depend = this.node._getDependComponent(this);
if (depend) {
return cc.errorID(3626,
cc.js.getClassName(this), cc.js.getClassName(depend));
}
}
if (this._super()) {
if (this._enabled && this.node._activeInHierarchy) {
cc.director._compScheduler.disableComp(this);
}
}
}
① disableComp
同禁用组件
· onDestroy() 的触发时机
当一个对象的 `destroy` 调用以后,会在当前帧结束后才真正销毁
注意:是当前帧结束后销毁,而不是下一帧销毁
相关源码路径:
resources\engine\cocos2d\core\platform\CCObject.js
deferredDestroy:销毁所有待销毁队列中的数据
function deferredDestroy () {
var deleteCount = objectsToDestroy.length;
for (var i = 0; i < deleteCount; ++i) {
var obj = objectsToDestroy[i];
if (!(obj._objFlags & Destroyed)) {
// 立即销毁
obj._destroyImmediate();
}
}
// if we called b.destory() in a.onDestroy(), objectsToDestroy will be resized,
// but we only destroy the objects which called destory in this frame.
if (deleteCount === objectsToDestroy.length) {
objectsToDestroy.length = 0;
} else {
objectsToDestroy.splice(0, deleteCount);
}
if (CC_EDITOR) {
deferredDestroyTimer = null;
}
}