Cocos2d之Node类详解之节点树(一)
一、声明
笔者分析的是用C++语言实现、版本号为cocos2d-x-3.3rc0的cocos2d框架的源代码。本文为笔者原创,允许读者分享和转载,只要读者注明文章来源即可。
二、简介
Node对象时场景图的基本元素,并且场景图的基本元素必须是Node对象和Node的子类对象。常见的Node类的子类有:Scene、Layer、Sprite、Menu和Label类。
Node类主要实现几个特性:
- Node对象的 addChild(Node *child)、getChildByTag(int tag)、removeChild(Node *child, bool cleanup=true) 能够使其持有别的Node对象作为其子节点。
- Node对象的调度器能够定时的调用毁掉函数。
- Node对象能够执行动作(动作由Action对象表示)。
Node子类一般实现下面几点:
- 重写Node类的init函数,使子类能够初始化资源和回调函数。
- 为Node子类编写回调函数,并交由调度器定时调用。
- 重写draw函数来渲染Node子类。
Node类有下面几个常用属性:
- position(位置)。此属性表示Node对象的中心点在坐标系中渲染的位置,默认初始化成(x = 0, y = 0)。
- anchor point(锚点)。默认为(x = 0, y = 0),但是Node的子类的初始值可能会有差异。
- scale(缩放)。默认宽和高的缩放比例都为1.
- rotation(旋转)。此属性表示顺时针旋转的角度,默认是0度。
- contentSize(内容大小)。默认长和宽都为0.
- visible(可见性)。默认为true。
这些属性会在后续的源码分析中做具体介绍。
三、源码详解
Node比较庞大,笔者打算在多篇博客中分别详细介绍Node节点的不同模块。前面说到Node对象能够持有其他Node对象作为其子节点,也就是说一个Node对象其实能够扩展出一个节点树。所以笔者先介绍节点树模块。
节点树实现
添加子节点
添加子节点的过程需要到下面的属性。
int _localZOrder; ///< Local order (relative to its siblings) used to sort the node float _globalZOrder; ///< Global order used to sort the node Vector<Node*> _children; ///< array of children nodes Node *_parent; ///< weak reference to parent node int _tag; ///< a tag. Can be any number you assigned just to identify this node std::string _name; ///<a string label, an user defined string to identify this node int _orderOfArrival; ///< used to preserve sequence while sorting children with the same localZOrder bool _running; ///< is running
下面看此addChild函数的声明。
/** * Adds a child to the container with z order and tag * * If the child is added to a 'running' node, then 'onEnter' and 'onEnterTransitionDidFinish' will be called immediately. * * @param child A child node * @param zOrder Z order for drawing priority. Please refer to `setLocalZOrder(int)` * @param tag An integer to identify the node easily. Please refer to `setTag(int)` * * Please use `addChild(Node* child, int localZOrder, const std::string &name)` instead. */ virtual void addChild(Node* child, int localZOrder, int tag); /** * Adds a child to the container with z order and tag * * If the child is added to a 'running' node, then 'onEnter' and 'onEnterTransitionDidFinish' will be called immediately. * * @param child A child node * @param zOrder Z order for drawing priority. Please refer to `setLocalZOrder(int)` * @param name A string to identify the node easily. Please refer to `setName(int)` * */ virtual void addChild(Node* child, int localZOrder, const std::string &name);
LocalZOrder参数决定了子节点被添加到节点树的位置,子节点在节点树中的位置决定了节点显示的顺序。关于节点树的遍历会在后续的博客中介绍,读者现在只需要知道LocalZOrder取值从负轴到正轴,显示顺序递减。
函数声明还提到,如果当前父节点处于running状态,那么被添加的子节点会被立刻调用onEnter和onEnterTransitionDidFinish函数。下面看此函数的具体实现。
void Node::addChild(Node *child, int localZOrder, int tag) { CCASSERT( child != nullptr, "Argument must be non-nil"); CCASSERT( child->_parent == nullptr, "child already added. It can't be added again"); addChildHelper(child, localZOrder, tag, "", true); } void Node::addChild(Node* child, int localZOrder, const std::string &name) { CCASSERT(child != nullptr, "Argument must be non-nil"); CCASSERT(child->_parent == nullptr, "child already added. It can't be added again"); addChildHelper(child, localZOrder, INVALID_TAG, name, false); }
这两个函数都调用了一个私有函数 void addChildHelper(Node* child, int localZOrder, int tag, const std::string &name, bool setTag)。下面看该函数的实现。
void Node::addChildHelper(Node* child, int localZOrder, int tag, const std::string &name, bool setTag) { if (_children.empty()) { this->childrenAlloc(); } this->insertChild(child, localZOrder); if (setTag) child->setTag(tag); else child->setName(name); child->setParent(this); /* 笔者注 * 设置节点到达顺序,如果节点树中不同节点有相同的LocalZOrder时, * 到达顺序小的节点先画 */ child->setOrderOfArrival(s_globalOrderOfArrival++); /* 笔者注 * 如果使用了物理引擎,需要为节点添加物理世界的性质 */ #if CC_USE_PHYSICS // Recursive add children with which have physics body. Scene* scene = this->getScene(); if (scene != nullptr && scene->getPhysicsWorld() != nullptr) { child->updatePhysicsBodyTransform(scene); scene->addChildToPhysicsWorld(child); } #endif if( _running ) { child->onEnter(); // prevent onEnterTransitionDidFinish to be called twice when a node is added in onEnter if (_isTransitionFinished) { child->onEnterTransitionDidFinish(); } } if (_cascadeColorEnabled) { updateCascadeColor(); } if (_cascadeOpacityEnabled) { updateCascadeOpacity(); } }
从实现源码不难看出,所有的子节点都被保存到 _children 数组中。如果父节点不处于 _running 状态,那么子节点在添加时就不会被调用 onEnter和onEnterTransitionDidFinished函数,这会产生什么影响笔者今后再做补充。
四、结束
本文就先介绍Node类实现往父节点的节点树添加子节点的过程。下一篇博客会继续介绍Node类节点树的实现。