zoukankan      html  css  js  c++  java
  • Cocos2d-x 3.2:UI树

    Cocos2d-x 3.2:UI树

    本文参考与深入理解Cocos2d-x 3.x:UI树一文

    Cocos2d-x 3.x 引擎的UI树系统

    首先得普及一下Cocos2d-x的基础概念,Cocos2d- x的游戏世界一般是由一个又一个的场景(Sence)组成的,比如登录是一个场景,战斗是一个场景;然后场景之下分为一个又一个的层(Layer),比如 界面层,地图层;层下又分为一个又一个的精灵、UI控件以及各类的界面元素。而以上种种都是基于一个叫做Node的基类。
    好了,回顾完了。下面我们来看看这个Node究竟做了什么(下面的声明忽略的部分无关函数):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    class CC_DLL Node : public Ref  
    {  
    public:  
        ////// ADD //////  
        virtual void addChild(Node * child);  
        virtual void addChild(Node * child, int localZOrder);  
        virtual void addChild(Node* child, int localZOrder, int tag);  
        virtual void addChild(Node* child, int localZOrder, const std::string &name);  
       
        ////// GET //////  
        virtual Node * getChildByTag(int tag) const;  
        virtual Node* getChildByName(const std::string& name) const;  
        template <typename T>  
        inline T getChildByName(const std::string& name) const return static_cast<T>(getChildByName(name)); }  
        virtual void enumerateChildren(const std::string &name, std::function<bool(Node* node)> callback) const;  
        virtual Vector<Node*>& getChildren() { return _children; }  
        virtual const Vector<Node*>& getChildren() const return _children; }  
        virtual ssize_t getChildrenCount() const;  
        virtual Node* getParent() { return _parent; }  
        virtual const Node* getParent() const return _parent; }  
       
       
        ////// REMOVES //////  
        virtual void removeFromParent();  
        virtual void removeFromParentAndCleanup(bool cleanup);  
        virtual void removeChild(Node* child, bool cleanup = true);  
        virtual void removeChildByTag(int tag, bool cleanup = true);  
        virtual void removeChildByName(const std::string &name, bool cleanup = true);  
        virtual void removeAllChildren();  
        virtual void removeAllChildrenWithCleanup(bool cleanup);  
    }

    可以看出大致分为3类方法,第一个是addChild,作用是为此节点添加子节点,当我们需要将一个精灵添加到层中时,我们可以这么做

    1
    2
    3
    4
    Layer l = Layer::create();  
    Sprite s = Sprite::create();  
    l->addChild(s);  
    this->addChild(l);

    addChild还有其他的几个变种,这里就不多解释了。addChild是UI树的重要组成部分之一,它将一个节点添加到UI树中,UI树会保持对这个节点的强引用(关于内存部分,下篇文章会说到)。

    第二个重要的方法是getXXX,相信用的人也挺多了,这里就不多说了,着重说下新冒出来的吧,分别是:

    1
    2
    3
    template <typename T>  
    inline T getChildByName(const std::string& name) const return static_cast<T>(getChildByName(name)); }  
    virtual void enumerateChildren(const std::string &name, std::function<bool(Node* node)> callback) const;

    第一个getChildByName<T>和普通的getChildByName其实差不多,看实现就知道了,只不过这个增加了一个模板,不用手动写静态转换。使用方法也差不多,例如,我想要获取到一个层下名字叫“exit”的精灵节点,就可以这么用:

    1
    l->getChildByName<Sprite>("exit");

    是不是稍微简洁点?

    再 说说另外一个函数enumerateChildren,这个函数就非常给力了,这个函数会搜索当前节点下的所有子节点,只要节点下的子节点的名字与参数 name一致,就会执行第二个参数的回调函数callback(当然,这函数还不止那么简单)。这里不做更多的分析(以后会单独出一篇文章对这个函数进行 具体的分析,这个函数也是一个非常有意思的函数啊,= =)

    接下来的函数就是removeXXXX了,这个函数的作用就是从UI树中移除某个节点,例如removeFromParent就是从UI数中将自己从父节点中移除。removeChild就是将参数中的子节点从UI树中移除。

    OK,基本函数已经介绍完毕;下面来说说UI树的组成。 一般而言,Scene就是UI树的根节点,而Layer节点一般作为Scene下的节点,Layer可以嵌套Layer;Layer也有许多变种,例如 LayerColor。Layer上可以添加各种图片以及控件,这些图片和控件上也可以添加各类子节点,由于所有的这些的基类都是Node,所以添加起来 非常自由,但是需要记住的是,虽然可以把Layer添加到一个Sprite上,但是这是一个在正常需求下不太合乎逻辑的事情。

    上述基本就是Cocos2d-x 3.x引擎的UI树系统,合理的使用这个UI树系统将会加快游戏开发的进度以及提高游戏的稳定性。


    UI树的内存管理机制,及如何利用UI树对游戏中的UI内存进行合理管理

    说到UI树的内存管理机制,就不得不提Cocos2d-x的内存管理机制——引用计数了,相信只要不是初学者都已经理解了这一块了,这里还是对Cocos2d-x的内存管理机制做一个大概的介绍吧。

    Cocos2d- x采用的是引用计数法作为其内存管理的方法,引用计数法的核心思想为,当某个类需要引用变量x时,需要增加一个变量x的引用计数,当这个类不需要变量x 时,需要减少一个变量x的引用计数。这样,谁引用,谁释放,引用和释放成对出现,就可以避免掉内存泄露的问题了。Cocos2d-x对这一方法还有一个扩 展,就是自动延迟释放机制,就是,如果存在一个变量x,它在函数的一帧上都需要用,但是下一帧,变量x就可以被释放掉,如果我们手动的在下一帧上释放x, 操作其他会非常麻烦,也不直观,Cocos2d-x提供了一个函数:autorelease,这个函数将会把对象加入到默认的自动释放池中,在一帧结束, 引擎将自动清理自动释放池中的变量内存,这样就非常方便了。

    Cocos2d-x有一个不成文的规定,指针的创建一般不会用new直接创建,而是通过一个方法:create来创建,这个方法已经被引擎封装成一个宏定义了:CREATE_FUNC,下面是这个宏定义的实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #define CREATE_FUNC(__TYPE__)   
    static __TYPE__* create()   
    {   
        __TYPE__ *pRet = new __TYPE__();   
        if (pRet && pRet->init())   
        {   
            pRet->autorelease();   
            return pRet;   
        }   
        else   
        {   
            delete pRet;   
            pRet = NULL;   
            return NULL;   
        }   
    }

    其 他函数我们不分析,可以看到它在其中首先new了这个类__TYPE__, 这时候new出来的对象的引用计数为1,然后初始化完成后,这里执行了autorelease,这时候引用计数仍然为1,但是引擎将其加入了自动释放池, 在这一帧结束的时候,这个对象的引用计数将变为0,引用计数为0的对象将会被释放掉。

    上述很啰嗦的介绍了一下Cocos2d-x的内存管理 机制,现在进入正文了,当一个节点被加入到UI树中,它的引用计数将会有怎么样的变化呢?下面是Node的addChild的源码分析(addChild 中真正的实现在addChildHelper中,下文忽略了不相关的代码):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    void Node::addChildHelper(Node* child, int localZOrder, int tag, const std::string &name, bool setTag)  
    {  
        this->insertChild(child, localZOrder);  
           
        if (setTag)  
            child->setTag(tag);  
        else  
            child->setName(name);  
           
        child->setParent(this);  
        child->setOrderOfArrival(s_globalOrderOfArrival++);  
    }

    可以看到真正的实现是在insertChild这个函数中的,我们继续尾随进去:

    1
    2
    3
    4
    5
    6
    7
    void Node::insertChild(Node* child, int z)  
    {  
        _transformUpdated = true;  
        _reorderChildDirty = true;  
        _children.pushBack(child);  
        child->_setLocalZOrder(z);  
    }

    好艰辛,终于看到了什么,这里将child加入到了_children中,_children是什么呢? 看它的声明

    1
    Vector<Node*> _children;

    注 意,这是一个大写V开头的Vector,说明这是Cocos2d-x自己实现的可变数组,这个数组实际上和std标准库中的数组的实现差不多,标准库的算 法可以完美的应用在这个数组上,这个数组与std::vector的最大区别就是引入了引用技术机制。在pushBack中,究竟做了些什么呢?

    1
    2
    3
    4
    5
    6
    void pushBack(T object)  
    {  
        CCASSERT(object != nullptr, "The object should not be nullptr");  
        _data.push_back( object );  
        object->retain();  
    }

    没错,重点在于这里

    1
    object->retain();

    它对于添加进来的对象都增加了引用,这样就说明,所有被加入UI树中的节点都会被UI树保持强引用。

    接下来对于removeXXXX函数进行分析,就挑选removeChild函数进行分析吧(其他函数也大同小异)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    void Node::removeChild(Node* child, bool cleanup /* = true */)  
    {  
        // explicit nil handling  
        if (_children.empty())  
        {  
            return;  
        }  
       
        ssize_t index = _children.getIndex(child);  
        if( index != CC_INVALID_INDEX )  
            this->detachChild( child, index, cleanup );  
    }

    而这个函数最终调用的是 detachChild函数,来继续跟踪进去吧(忽略的无关代码)

    1
    2
    3
    4
    5
    6
    7
    void Node::detachChild(Node *child, ssize_t childIndex, bool doCleanup)  
    {  
        // set parent nil at the end  
        child->setParent(nullptr);  
       
        _children.erase(childIndex);  
    }

    这里的重点代码就是

    1
    _children.erase(childIndex);

    同样跟踪进入看看它的实现:

    1
    2
    3
    4
    5
    6
    7
    iterator erase(ssize_t index)  
    {  
        CCASSERT(!_data.empty() && index >=0 && index < size(), "Invalid index!");  
        auto it = std::next( begin(), index );  
        (*it)->release();  
        return _data.erase(it);  
    }

    没错,它执行了下面这句代码:

    1
    (*it)->release();

    减少了对象的引用计数,这样就能将UI从UI树中分离并且不会造成内存泄露了。

    当然,这样做的好处还不止这些,试想如下代码

    1
    2
    3
    4
    5
    6
    7
    8
    Scene* s = Scene::create();  
    Director::getInstance()->runWithScene(s);  
    Layer* l = Layer::create();  
    s->addChild(l);  
       
    .... 若干帧后  
       
    s->removeChild(l);

    是否会造成内存泄露?

    答案是不会,而且这样写出来的代码,我们并不需要关心内存的分配问题,引擎会自动帮我们申请内存,并且在不需要的时候,自动将内存回收。这似乎是一个非常好的解决方案,但是也有一些不足。

    试想如下使用场景,现需要将上述的l节点与s节点中间增加一个层m,m是s场景的子节点,也是l层的父节点,这时候应该怎么做呢?要知道在removeChild之后,l层的内存已经被释放掉了。似乎没有什么解决方法了,看下文:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    Scene* s = Scene::create();  
    Director::getInstance()->runWithScene(s);  
    Layer* l = Layer::create();  
    l->setTag(1);  
    s->addChild(l);  
       
    .... 若干帧后  
       
    auto l = s->getChildByTag(1);  
    l->retain();  
    s->removeChild(l);  
    Layer* m = Layer::create();  
    s->addChild(m);  
    m->addChild(l);  
    l->release();

    这 样提前将l取出来增加一个引用计数就可以避免l的内存被UI树释放掉了,但是值得注意的是,retain方法必须与release方法对应出现,否则会造 成内存泄露。但是开发者往往会忘记写后面的release从而造成内存泄露,那么怎么避免这样的情况出现呢,答案是:智能指针。看下面的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Scene* s = Scene::create();  
    Director::getInstance()->runWithScene(s);  
    Layer l = Layer::create();  
    l->setTag(1);  
    s->addChild(l);  
       
    .... 若干帧后  
       
    RefPtr<Node*> l = s->getChildByTag(1);  
    s->removeChild(l);  
    Layer m = Layer::create();  
    s->addChild(m);  
    m->addChild(l);

    非常简单方便,完全不需要关心内存的申请和释放,关于智能指针部分以后会对其做出分析。

  • 相关阅读:
    使用srvany.exe将任何程序作为Windows服务运行
    instsrv.exe用法
    在博客园中发现的一篇文章,感觉这些内容就是我心中所想表达的!
    HTML5的Video标签的属性,方法和事件汇总
    使用nodejs 来压缩整个目录
    git 基础
    mac 上安装 redis
    第12次实验总结
    第12次实验作业
    第十一次实验总结
  • 原文地址:https://www.cnblogs.com/dudu580231/p/4560037.html
Copyright © 2011-2022 走看看