开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情
官方文档:
生命周期回调:
测试脚本
const { ccclass, property } = cc._decorator;@ccclassexport 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、onDestroy 依赖 节点
onEnable、start、update、lateUpdate、onDisable 依赖 组件
它们的区别在于:
如果脚本组件所在节点的 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; }}
更多教程请扫码关注