Cocos Creator 脚本组件的生与死!

COCOS

共 15738字,需浏览 32分钟

 · 2021-05-12

目前 Creator 提供给用户的生命周期回调函数主要有:

onLoad
start
update
lateUpdate
onDestroy
onEnable
onDisable


测试脚本


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 依赖节点

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; }}

官方文档:

生命周期回调:

https://docs.cocos.com/creator/manual/zh/scripting/life-cycle-callbacks.html


往期精彩

浏览 179
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报