zoukankan      html  css  js  c++  java
  • 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类节点树的实现。

  • 相关阅读:
    LeetCode_35.搜索插入位置
    LeetCode_349.两个数组的交集
    LeetCode_344.反转字符串
    LeetCode_34.在排序数组中查找元素的第一个和最后一个位置
    LeetCode_303.区域和检索
    LeetCode_3.无重复字符的最长子串
    LeetCode_292.Nim 游戏
    LeetCode_283.移动零
    LeetCode_27.移除元素
    LeetCode_268.丢失的数字
  • 原文地址:https://www.cnblogs.com/chenshi/p/4086277.html
Copyright © 2011-2022 走看看