一、声明
本文属于笔者原创,允许读者转载和分享,只要注明文章来源即可。
笔者使用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类实现节点树的添加、获取、删除子节点等功能的内容就到此结束咯。