首页 > 代码库 > Cocos2d之Node类详解之节点树(一)

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 nodefloat _globalZOrder;            ///< Global order used to sort the nodeVector<Node*> _children;        ///< array of children nodesNode *_parent;                  ///< weak reference to parent nodeint _tag;                         ///< a tag. Can be any number you assigned just to identify this nodestd::string _name;               ///<a string label, an user defined string to identify this nodeint _orderOfArrival;            ///< used to preserve sequence while sorting children with the same localZOrderbool _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类节点树的实现。

Cocos2d之Node类详解之节点树(一)