一、控件和组件神秘奥义


控件: 控件是指对数据和方法的封装。控件可以有自己的属性和方法,其中属性是控件数据的简单访问者,方法则是控件的一些简单而可见的功能、控件创建过程包括设计、开发、调试(就是所谓的3Ds开发流程,即Design、Develop、Debug)工作, 然后是控件的使用。(来自百度百科)


组件: 系统中一种物理的、可代替的部件、它封装了实现并提供了一系列可用的接口。一个组件代表一个系统中实现的物理部分,包括软件代码(源代码,二进制代码,可执行代码)或者一些类似内容,如脚本或者命令文件。简而言之,组件就是对象,是对数据和方法的简单封装。C++ Builder中叫组件,Delphi中叫部件,而在Visual BASIC中叫控件。 组件可以有自己的属性和方法。属性是组件数据的简单访问者。方法则是组件的一些简单而可见的功能。(来自百度百科)


二、UI和代码结合的弊端


1. 大量通过界面引用UI节点


如下图所示,通过脚本组件大量直接引用UI节点,会在相应的描述文件中记录相应的keyvalue,在一定程度上会增加相关描述文件的大小,从而影响这个描述文件加载所花费的时间。进一步影响资源加载总时间。


{
    "__type__": "85d24iUBchG45eRtzPrngjE", 
    "_name": "", 
    "_objFlags": 0, 
    "node": {
        "__id__": 1
    }, 
    "_enabled": true, 
    "label": {
        "__id__": 7
    }, 
    "node1": {
        "__id__": 9
    }, 
    "node2": {
        "__id__": 12
    }, 
    "_id": ""}


通过脚本组件大量直接引用UI节点这种方式还有一个弊端,当节点上的脚本组件因某种原因丢失或者说被重置了,那么还需要被重新挂载一次,如果时间长了,可能都忘记了谁对应的谁。


2.代码中大量查找节点


第一种方式,代码中通过 getChildByName, getChildByUuid 函数查到相应的节点, 在此以 getChildByName 函数来举例。通过下面代码可以看出,正序遍历查找相应节点的名字,返回第一个查到的节点。

 /**
 * !#en Returns a child from the container given its name.
 * !#zh 通过名称获取节点的子节点。
 * @method getChildByName
 * @param {String} name - A name to find the child node.
 * @return {Node} a CCNode object whose name equals to the input parameter
 * @example
 * var child = node.getChildByName("Test Node");
 */
getChildByName (name) {
    if (!name) {
        cc.log("Invalid name");
        return null;
    }
    var locChildren = this._children;
    for (var i = 0, len = locChildren.length; i < len; i++) {
        if (locChildren[i]._name === name)
            return locChildren[i];
    }
    return null;
}


举个例子:如下所示,一个根节点下依次有节点A,节点B,节点C,节点D 四个子节点。现在通过 getChildByName 来获取 节点D。

-根节点
--子节点A
--子节点B
--子节点C
--子节点D


如果现在需要查找子节点A呢:

      第一次遍历: 查找子节点A,满足条件,则返回子节点A

也就是说 getChildByName 查找子节点A的时间复杂度为1。


如果现在需要查找子节点D呢:

      第一次遍历: 查找子节点A,不满足条件,则继续遍历查找

      第二次遍历: 查找子节点B,不满足条件,则继续遍历查找

      第三次遍历: 查找子节点C,不满足条件,则继续遍历查找

      第四次遍历: 查找子节点D,不满足条件,则返回子节点D

也就是说 getChildByName 查找子节点A的时间复杂度为4。


综上所述: getChildByName 查找相应的节点的时间复杂度为 N。意味得查找的节点广度大,所需要的时间越长。代码中大量通过 getChildByName , getChildByUuid 函数查到相应的节点是一个影响性能的因子。


第二种方式,代码中通过 cc.find 函数查到相应的节点,从下面代码可以看出,两层正序遍历查找相应的节点。第一层正序遍历的因子是传入的路径分割。第二层正序遍历的因子是节点的children。


/**
 * Finds a node by hierarchy path, the path is case-sensitive.
 * It will traverse the hierarchy by splitting the path using '/' character.
 * This function will still returns the node even if it is inactive.
 * It is recommended to not use this function every frame instead cache the result at startup.
 *
 * @method find
 * @static
 * @param {String} path
 * @param {Node} [referenceNode]
 * @return {Node|null} the node or null if not found
 */
cc.find = module.exports = function (path, referenceNode) {
    if (path == null) {
        cc.errorID(5600);
        return null;
    }
    if (!referenceNode) {
        var scene = cc.director.getScene();
        if (!scene) {
            if (CC_DEV) {
                cc.warnID(5601);
            }
            return null;
        }
        else if (CC_DEV && !scene.isValid) {
            cc.warnID(5602);
            return null;
        }
        referenceNode = scene;
    }
    else if (CC_DEV && !referenceNode.isValid) {
        cc.warnID(5603);
        return null;
    }
    var match = referenceNode;
    var startIndex = (path[0] !== '/') ? 0 : 1; // skip first '/'
    var nameList = path.split('/');
    // parse path
    for (var n = startIndex; n < nameList.length; n++) {
        var name = nameList[n];
        var children = match._children;
        match = null;
        for (var t = 0, len = children.length; t < len; ++t) {
            var subChild = children[t];
            if (subChild.name === name) {
                match = subChild;
                break;
            }
        }
        if (!match) {
            return null;
        }
    }
    return match;
};


举个例子:如下所示,现在一个场景上的节点关系如下。

-场景根节点
--子节点A
---子节点A1
--子节点B
--子节点C
---子节点C1
--子节点D
---子节点D1
---子节点D2


如果现在需要查找子节点A1呢(暂不考虑第二个参数的情况下):

cc.find('子节点A/子节点A1') 开始遍历是 names  = [子节点A,子节点A1]

第一层第一次遍历: 找到子节点A,满足条件,则进行第二层遍历。

第二层第一次遍历: 找到子节点A1,满足条件,则返回。


也就是说 cc.find 查找子节点A1 的时间复杂度为2。


如果现在需要查找子节点D2呢(暂不考虑第二个参数的情况下):

cc.find('子节点D/子节点D2') 开始遍历是 names  = [子节点D,子节点D2]

第一层第一次遍历: 找到子节点A,不满足条件,则继续遍历。

第一层第一次遍历: 找到子节点B,不满足条件,则继续遍历。

第一层第一次遍历: 找到子节点C,不满足条件,则继续遍历。

第一层第一次遍历: 找到子节点D,满足条件,则进行第二层遍历。

第二层第一次遍历: 找到子节点D1,不满足条件,则继续。

第二层第一次遍历: 找到子节点D2,满足条件,则返回。

也就是说 cc.find 查找子节点D2的时间复杂度为6


综上所述: cc.find 查找一次节点的时间复杂度为 C(2 n)=n*(n-1)/(2*1)。意味着查找的节点广度越大和深度越深所需要的时间越长。代码中大量通过 cc.find 函数查到相应的节点是一个影响性能的因子。


无论是第一种方式还是第二种方式在代码中引用相关的节点,如果由于某种原因,修改了节点之间的关系,进而查不到相应的节点引发的错误。还需要重新修改查找相应节点的路径。


3. 大量通过界面引用UI节点 和 代码中大量查找节点


通过这种方式来查找节点,同时具有上面两种方法的弊端。更重要的是同时通过界面引用UI节点和代码中查找UI节点,是不是感觉有点乱。


关注【游戏讲坛】微信公众号,获取最新动态!