Cocos2d之Node類詳解之節點樹(二)


一、聲明

本文屬於筆者原創,允許讀者轉載和分享,只要注明文章來源即可。

筆者使用cocos2d框架的cocos2d-x-3.3rc0版本的源代碼做分析。這篇文章承接上篇《Cocos2d之Node類詳解之節點樹(一)》

二、簡介

節點

一個Node對象。

節點樹

上篇文章介紹到,Node類有一個成員變量 Vector<Node*> _children,這是一個保存所有子節點的數組,因為Node類采用遍歷樹的方式獲取子節點進行渲染,所以我管這兩個東西的結合叫節點樹。

三、源碼詳解

Cocos2d之Node類詳解之節點樹(一)》一文中已經介紹了Node對象如何往節點樹中添加子節點,現在介紹從節點樹中獲取節點和刪除節點的實現過程。

獲取子節點

相關函數的聲明:

/**
     * Gets a child from the container with its tag
     *
     * @param tag   An identifier to find the child node.
     *
     * @return a Node object whose tag equals to the input parameter
     *
     * Please use `getChildByName()` instead
     */
     virtual Node * getChildByTag(int tag) const;
    /**
     * Gets a child from the container with its name
     *
     * @param name   An identifier to find the child node.
     *
     * @return a Node object whose name equals to the input parameter
     *
     * @since v3.2
     */
    virtual Node* getChildByName(const std::string& name) const;

相關函數實現:

Node* Node::getChildByTag(int tag) const
{
    CCASSERT( tag != Node::INVALID_TAG, "Invalid tag");

    for (auto& child : _children)
    {
        if(child && child->_tag == tag)
            return child;
    }
    return nullptr;
}

Node* Node::getChildByName(const std::string& name) const
{
    CCASSERT(name.length() != 0, "Invalid name");
    
    std::hash<std::string> h;
    size_t hash = h(name);
    
    for (const auto& child : _children)
    {
        // Different strings may have the same hash code, but can use it to compare first for speed
        if(child->_hashOfName == hash && child->_name.compare(name) == 0)
            return child;
    }
    return nullptr;
}

從源碼可以看出,Node類提供分別以name、tag為關鍵字查詢子節點的方式。每種方式的實現都是對 _children 子節點數組進行遍歷匹配。值得注意的是,在 getChildByName 函數中,為了提高字符串匹配的效率,先進行哈希匹配再進行字符串內容對比。

 

刪除子節點

相關函數聲明。

/**
     * Removes a child from the container. It will also cleanup all running actions depending on the cleanup parameter.
     *
     * @param child     The child node which will be removed.
     * @param cleanup   true if all running actions and callbacks on the child node will be cleanup, false otherwise.
     */
    virtual void removeChild(Node* child, bool cleanup = true);

    /**
     * Removes a child from the container by tag value. It will also cleanup all running actions depending on the cleanup parameter
     *
     * @param tag       An interger number that identifies a child node
     * @param cleanup   true if all running actions and callbacks on the child node will be cleanup, false otherwise.
     *
     * Please use `removeChildByName` instead.
     */
     virtual void removeChildByTag(int tag, bool cleanup = true);
    /**
     * Removes a child from the container by tag value. It will also cleanup all running actions depending on the cleanup parameter
     *
     * @param name       A string that identifies a child node
     * @param cleanup   true if all running actions and callbacks on the child node will be cleanup, false otherwise.
     */
    virtual void removeChildByName(const std::string &name, bool cleanup = true);
    /**
     * Removes all children from the container with a cleanup.
     *
     * @see `removeAllChildrenWithCleanup(bool)`
     */
    virtual void removeAllChildren();

刪除子節點函數實現。

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

void Node::removeChildByTag(int tag, bool cleanup/* = true */)
{
    CCASSERT( tag != Node::INVALID_TAG, "Invalid tag");

    Node *child = this->getChildByTag(tag);

    if (child == nullptr)
    {
        CCLOG("cocos2d: removeChildByTag(tag = %d): child not found!", tag);
    }
    else
    {
        this->removeChild(child, cleanup);
    }
}

void Node::removeChildByName(const std::string &name, bool cleanup)
{
    CCASSERT(name.length() != 0, "Invalid name");
    
    Node *child = this->getChildByName(name);
    
    if (child == nullptr)
    {
        CCLOG("cocos2d: removeChildByName(name = %s): child not found!", name.c_str());
    }
    else
    {
        this->removeChild(child, cleanup);
    }
}

void Node::removeAllChildren()
{
    this->removeAllChildrenWithCleanup(true);
}

從源碼中可以看出,addChild函數並沒有直接將子節點從 _children 數組中刪除,而是獲取子節點在 _children 數組中的位置,然后調用 detachChild( child, index, cleanup )函數。detachChild函數的聲明如下:

/// Removes a child, call child->onExit(), do cleanup, remove it from children array.
void detachChild(Node *child, ssize_t index, bool doCleanup);

該函數的實現如下:

void Node::detachChild(Node *child, ssize_t childIndex, bool doCleanup)
{
    // IMPORTANT:
    //  -1st do onExit
    //  -2nd cleanup
    if (_running)
    {
        child->onExitTransitionDidStart();
        child->onExit();
    }
    
#if CC_USE_PHYSICS
    child->removeFromPhysicsWorld();
#endif

    // If you don't do cleanup, the child's actions will not get removed and the
    // its scheduledSelectors_ dict will not get released!
    if (doCleanup)
    {
        child->cleanup();
    }

    // set parent nil at the end
    child->setParent(nullptr);

    _children.erase(childIndex);
}

detachChild 函數其實就是去釋放子節點擁有的資源,這種設置是很合理的。

節點樹重排序

相關函數聲明:

/**
     * Reorders a child according to a new z value.
     *
     * @param child     An already added child node. It MUST be already added.
     * @param localZOrder Z order for drawing priority. Please refer to setLocalZOrder(int)
     */
    virtual void reorderChild(Node * child, int localZOrder);

    /**
     * Sorts the children array once before drawing, instead of every time when a child is added or reordered.
     * This appraoch can improves the performance massively.
     * @note Don't call this manually unless a child added needs to be removed in the same frame
     */
    virtual void sortAllChildren();

函數實現:

void Node::reorderChild(Node *child, int zOrder)
{
    CCASSERT( child != nullptr, "Child must be non-nil");
    _reorderChildDirty = true;
    child->setOrderOfArrival(s_globalOrderOfArrival++);
    child->_localZOrder = zOrder;
}

void Node::sortAllChildren()
{
    if( _reorderChildDirty ) {
        std::sort( std::begin(_children), std::end(_children), nodeComparisonLess );
        _reorderChildDirty = false;
    }
}

值得注意的是, reorderChild 函數只是改變子節點 _localZOrder 的值,還有將父節點的 _reorderChildDirty 標志位置true。_reorderChildDirty 標志位為true說明子節點的 _localZOrder 發生了改變,因此需要調用 sortAllChildren 函數對所有子節點進行排序。還有一點要注意的是,改變一次子節點的_localZOrder時,s_globalOrderOfArrival屬性加1(這個屬性的介紹在《Cocos2d之Node類詳解之節點樹(一)》添加子節點部分由介紹)。

sortAllChildren函數使用了C++標准庫提供的 sort函數對所有子節點進行排序。筆者通過下面這個例子詳細為讀者解釋這個函數的用法。

#include <iostream>     // std::cout
#include <algorithm>    // std::sort
#include <vector>       // std::vector

bool myfunction (int i,int j) { return (i<j); }

struct myclass {
  bool operator() (int i,int j) { return (i<j);}
} myobject;

int main () {
  int myints[] = {32,71,12,45,26,80,53,33};
  std::vector<int> myvector (myints, myints+8);               // 32 71 12 45 26 80 53 33

  // using default comparison (operator <):
  std::sort (myvector.begin(), myvector.begin()+4);           //(12 32 45 71)26 80 53 33

  // using function as comp
  std::sort (myvector.begin()+4, myvector.end(), myfunction); // 12 32 45 71(26 33 53 80)

  // using object as comp
  std::sort (myvector.begin(), myvector.end(), myobject);     //(12 26 32 33 45 53 71 80)
  return 0;
}

了解了 sort 函數的用法之后,我們看Node類是怎么具體使用sort函數的吧。

void Node::sortAllChildren()
{
    if( _reorderChildDirty ) {
        std::sort( std::begin(_children), std::end(_children), nodeComparisonLess );
        _reorderChildDirty = false;
    }
}

bool nodeComparisonLess(Node* n1, Node* n2)
{
    return( n1->getLocalZOrder() < n2->getLocalZOrder() ||
           ( n1->getLocalZOrder() == n2->getLocalZOrder() && n1->getOrderOfArrival() < n2->getOrderOfArrival() )
           );
}

 

四、結束

介紹Node類實現節點樹的添加、獲取、刪除子節點等功能的內容就到此結束咯。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM