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类节点树的实现。

原文地址:https://www.cnblogs.com/chenshi/p/4086277.html