Cocos Creator | 脚本组件的生与死-生命周期函数回调触发时机

更舒适的阅读体验,请访问公众号:
 

 

官方文档:

 

生命周期回调:

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

 

测试脚本

 

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;    }}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值