Cocos2d-X3.0 刨根問底(五)----- Node類及顯示對象列表源碼分析


    上一章 我們分析了Cocos2d-x的內存管理,主要解剖了 Ref、PoolManager、AutoreleasePool這三個類,了解了對象是如何自動釋放的機制。之前有一個類 Node經常出現在各種場合,不是做為參數就是做為返回值,那么這一章節我們就去看看這個Node類到底在Cocos2d-x里處於一個什么樣的地位。

    直接進入主題,我們打開CCNode.h文件。我去,這個文件有1500行,這么長怎么看啊,放松一下整體看過一遍,松了一口氣,還好,還沒那么糟,文件雖然大,注釋占了有90%的篇幅,代碼不多,注釋多正好 方便我們閱讀,信心來了。

    CCNode.h文件里面一共定義了兩個類 Node與__NodeRGBA類,從命名上可以看得出 __NodeRGBA類肯定是依靠Node類,或者說是Node類的一個擴展,那么好我們開始看一下今天的主角 Node。

    老規矩,在看Node類之前我們先看一下這個文件里面的頭文件和引用的類都有哪些,通過這些信息能讓我們更好的了解Node類和其它類的關系。

 

#include "ccMacros.h"
#include "CCAffineTransform.h"
#include "CCGL.h"
#include "ccGLStateCache.h"
#include "CCGLProgram.h"
#include "CCScriptSupport.h"
#include "CCProtocols.h"
#include "CCEventDispatcher.h"
#include "CCVector.h"
#include "kazmath/kazmath.h"

NS_CC_BEGIN

class GridBase;
class Point;
class Touch;
class Action;
class LabelProtocol;
class Scheduler;
class ActionManager;
class Component;
class ComponentContainer;
class EventDispatcher;
class Scene;
class Renderer;
#if CC_USE_PHYSICS
class PhysicsBody;
#endif

/**
 * @addtogroup base_nodes
 * @{
 */

enum {
    kNodeOnEnter,
    kNodeOnExit,
    kNodeOnEnterTransitionDidFinish,
    kNodeOnExitTransitionDidStart,
    kNodeOnCleanup
};

bool nodeComparisonLess(Node* n1, Node* n2);

class EventListener;

 

果然這個Node類是個重要的角色啊 下面簡要分析一下引入的類

 

GridBasePoint 自己定義 的數據類型,格子與點

Touch 應該和手機觸摸事件相關的

Action 動作相關

LabelProtocol 標簽UI接口

Scheduler 調度控制

ActionManager 動作管理器 (動作這部分后面我們專門的章節來討論)

Component 組件(是什么組件呢?不看代碼不知道啊,之后碰到了再分析源碼)

ComponentContainer (組件容器,貌似和UI有關系)

EventDispatcherEventListener 這是事件相關的(事件我們也分章節討論)

Scene 場景

Renderer 渲染相關

 

還定義了幾個事件,用了一個枚舉 kNodeOnEnter    kNodeOnExit    kNodeOnEnterTransitionDidFinish     kNodeOnExitTransitionDidStart    kNodeOnCleanup

bool nodeComparisonLess(Node* n1, Node* n2);  這個函數看起來是一個謂詞函數用來比較兩個Node類的大小

到目前為止,Node類涉及到的元素我們也有了初步了解,接下來我們看一下Node類的定義

class CC_DLL Node : public Ref

Node類繼承了Ref類,采用了Cocos2d-x的內部內存管理機制,這個上一章節我們討論過不再詳述。

 

再看一下Node類所有成員變量,看看究竟Node都封裝了哪些東東。

類定義最先定義了一個類靜態變量值得我們注意一下。

static const int INVALID_TAG = -1;

這是所有Node類對象共用的一個屬性,從命令上可以知曉這個參數標記了 Node類是否有效,具體是干什么有效,后面 我們碰到使用這個變量的方法時就會知曉了,小魚看到這里也不是很清楚。碰到再說。(預測是標記着,渲染,事件,觸摸,等是否有效)。

再看其它屬性,咱們一個一個來看。

float _rotationX; /// 延X軸旋轉角度
float _rotationY; ///延Y軸旋轉角度

最先看到這兩個屬性,是延X,Y旋轉的角度順便翻看了一下這兩個屬性的get / set 方法

 

/**
     * Sets the rotation (X,Y,Z) in degrees.
     * Useful for 3d rotations
     */
    virtual void setRotation3D(const Vertex3F& rotation);
    /**
     * returns the rotation (X,Y,Z) in degrees.
     */
    virtual Vertex3F getRotation3D() const;

 

 

 

 

看到這兩個函數名,這真是個坑啊,原以為能命名成setRotationX或者setRotationY之類的,結果是兩個變量都在一個方法里進行get / set

這里出現了一個新碰到的結構Vertex3F這是一個以OpenGl命名習慣的定義 Vertex (頂點)  3F 需要3個float參數 ,這么一翻譯那么這是一個頂點的結構定義,隨便看下Vertex3F的結構體定義體會一下。

struct
Vertex3F { Vertex3F(float _x, float _y, float _z) : x(_x) , y(_y) , z(_z) {} Vertex3F(): x(0.f), y(0.f), z(0
.f) {} GLfloat x; GLfloat y; GLfloat z; };

Vertex3F就是一個三維數據結構體,沒什么深奧的。

為了理解好RotationX,RotationY 是什么樣的屬性,小魚做了一個小小的Demo幫助一起理解,這里放出Gif圖。

_rotationX 每100毫秒增加10度 呈現效果

rotationX

_rotationY 每100毫秒增加10度 呈現效果

rotationY

處於好奇,如果rotationX Y一起變化是個什么情況呢?

_rotationX _rotationY 每100毫秒各增加10度 呈現效果

rotationXY

哈哈,通過這幾個效果一目了然這兩個變量所起的作用了。

下面繼續看,第二組Node的成員變量

// rotation Z is decomposed in 2 to simulate Skew for Flash animations
float _rotationZ_X; /// < rotation angle on Z-axis, component X
float _rotationZ_Y; ///< rotation angle on Z-axis, component Y

 

又出來兩個旋轉屬性,有點蒙啊,仔細看下注釋,這兩個屬性是用來描述延Z轉旋轉時相對X,Y轉的分量……這是熊么玩意?

為了破解這個屬性的含義,我再從cpp文件里找一下關於這個屬性的 get / set方法。

找到如下方法

 

 /**
     * Sets the rotation (angle) of the node in degrees.
     *
     * 0 is the default rotation angle.
     * Positive values rotate node clockwise, and negative values for anti-clockwise.
     *
     * @param rotation     The rotation of the node in degrees.
     */
    virtual void setRotation(float rotation);
    /**
     * Returns the rotation of the node in degrees.
     *
     * @see `setRotation(float)`
     *
     * @return The rotation of the node in degrees.
     */
    virtual float getRotation() const;

 

這兩個方法是同時設置/獲得 _rotationZ_X和_rotationZ_Y 的值,並且使 _rotationZ_X = _rotationZ_Y

還有四個是單獨設置 _rotationZ_X與_rotationZ_Y的值的方法。

 

/**
     * Sets the X rotation (angle) of the node in degrees which performs a horizontal rotational skew.
     *
     * The difference between `setRotationalSkew()` and `setSkew()` is that the first one simulate Flash's skew functionality
     * while the second one uses the real skew function.
     *
     * 0 is the default rotation angle.
     * Positive values rotate node clockwise, and negative values for anti-clockwise.
     *
     * @param rotationX    The X rotation in degrees which performs a horizontal rotational skew.
     */
    virtual void setRotationSkewX(float rotationX);
    CC_DEPRECATED_ATTRIBUTE virtual void setRotationX(float rotationX) { return setRotationSkewX(rotationX); }

    /**
     * Gets the X rotation (angle) of the node in degrees which performs a horizontal rotation skew.
     *
     * @see `setRotationSkewX(float)`
     *
     * @return The X rotation in degrees.
     */
    virtual float getRotationSkewX() const;
    CC_DEPRECATED_ATTRIBUTE virtual float getRotationX() const { return getRotationSkewX(); }

    /**
     * Sets the Y rotation (angle) of the node in degrees which performs a vertical rotational skew.
     *
     * The difference between `setRotationalSkew()` and `setSkew()` is that the first one simulate Flash's skew functionality
     * while the second one uses the real skew function.
     *
     * 0 is the default rotation angle.
     * Positive values rotate node clockwise, and negative values for anti-clockwise.
     *
     * @param rotationY    The Y rotation in degrees.
     */
    virtual void setRotationSkewY(float rotationY);
    CC_DEPRECATED_ATTRIBUTE virtual void setRotationY(float rotationY) { return setRotationSkewY(rotationY); }

    /**
     * Gets the Y rotation (angle) of the node in degrees which performs a vertical rotational skew.
     *
     * @see `setRotationSkewY(float)`
     *
     * @return The Y rotation in degrees.
     */
    virtual float getRotationSkewY() const;
    CC_DEPRECATED_ATTRIBUTE virtual float getRotationY() const { return getRotationSkewY(); }

 

 

 

英文水平好的同學可以看看注釋加以理解

像小魚這樣英語四級考了八次都沒過的,可以參考下面的gif圖來理解這兩個變量究竟是來做什么的。

_rotationZ_X 每100毫秒增加10度 呈現效果

_rotationZ_X

_rotationZ_Y 每100毫秒增加10度 呈現效果

_rotationZ_Y

_rotationZ_X _rotationZ_Y一起改變 呈現效果

_rotationZ_XY

怎么樣,當_rotationZ_X與 _rotationZ_Y同時變化時就是在平面內旋轉 setRotation 方法就是設置旋轉的。

看了上面這些方法定義的時候我注意到了這樣一段代碼

以setRotationSkewX為例

 

void Node::setRotationSkewX(float rotationX)
{
    if (_rotationZ_X == rotationX)
        return;
    
    _rotationZ_X = rotationX;
    _transformUpdated = _transformDirty = _inverseDirty = true;
}

 

 

 

大家看一下標紅的這行代碼 設置了三個成員變量 _transformUpdated 、 _transformDirty、_inverseDirty 三個值為true;

再找一下這三個變量的聲明代碼

 

  // "cache" variables are allowed to be mutable
    mutable kmMat4 _transform;      ///< 變換矩陣
    mutable bool _transformDirty;   ///< 是否應該進行變換矩陣計算
    mutable kmMat4 _inverse;        ///< 倒轉變換矩陣
    mutable bool _inverseDirty;     ///< 標記是否應該進行倒轉計算
    mutable kmMat4 _additionalTransform; ///附加的變換矩陣
    bool _useAdditionalTransform;   ///< 使用附加變換
    bool _transformUpdated;         ///< 標記是否需要在場景更新的最后進行矩陣變換計算。

 

 

 

可以猜到,在設置了Node的一些旋轉屬性后同時會設置了一些矩陣計算的標識,用來標記是否進行旋轉后的矩陣計算。

繼續看Node類的屬性,我們看到了這面一組設置Node的縮放的變量

float _scaleX; /// < scaling factor on x-axis
float _scaleY; /// < scaling factor on y-axis
float _scaleZ; ///< scaling factor on z-axis

 

這個不用解釋太多,就是延 x, y, z軸的縮放比例

看一下對應這三個值的get/set方法

 

 /**
     * _scaleX的 get set 方法
     */
    virtual void setScaleX(float scaleX);
    virtual float getScaleX() const;


    /**
     * _scaleY的 get set 方法
     */
    virtual void setScaleY(float scaleY);
    virtual float getScaleY() const;

    /**
     * _scaleZ的get set 方法
     */
    virtual void setScaleZ(float scaleZ);
    virtual float getScaleZ() const;


    /**
     * 將_scaleX _scaleY  _scaleZ設置成相同值的 get set 方法
     */
    virtual void setScale(float scale);
    virtual float getScale() const;

 

 

 

下面我用一個圖來說明這幾個方法的作用

scale

縮放比例 當 0~1時是縮小  等於1時是原大小  大於1時是放大。

通過上在的圖可以看到實際上在z方向的縮放對Node實際上不起作用,很正常因為這是2D引擎。

繼續看Node類的屬性

Point _position; /// < node的位置
float _positionZ; ///< 在OpenGl里的 z位置

 

node的這兩個屬性一看就知道是什么意思了,就是標記這個node的位置的變量, 這兩個變量的 get set 方法很簡單就不跟進了。

這里出現了一個新的類型 Point 從命名上就可以知道這是記錄一個點的數據結構

我們看一下這個Point類是怎么定義的

class
CC_DLL Point { public : float x; float y;

這個類后面有一大堆的方法和運算符重載操作,有興趣的同學可以跟進看一下,大概就是操作這 x, y兩個值 的,有比較兩個point的方法求平方和,點乘,叉乘,等向量運算的方法,可以把它理解成一個點,也可以理解成一個向量,具體看實際應用。

再繼續向下看Node的成員變量

float _skewX; /// < skew angle on x-axis
float _skewY; ///< skew angle on y-axis

又出來了兩個形變的變量,從字面上理解是與x ,y 軸的角度

再跟進這個個變量的 get set 方法很簡單,就是普通的賦值與返回值。

我再用圖例來學習這個變量會影響到Node的什么屬性。

_skewX 每100毫秒增加10度 呈現效果

SkewX

_skewY 每100毫秒增加10度 呈現效果

SkewY

再看一下 skewX與 skewY一同變化的效果

SkewXY

不用多說,仔細觀察這些變化 就可以知道這兩個參數的作用了。

接下來是兩個有關錨點的成員變量

Point _anchorPointInPoints; /// < anchor point in points
Point _anchorPoint; /// < anchor point normalized (NOT in points)
Size _contentSize; ///< untransformed size of the node

什么是錨點呢?有過動畫制作經驗的同學可能了解,在這里小魚簡單提一下,錨點通俗一點理解就是圖形在做變形,旋轉時候的一個基准點,比如上面的一些gif圖形進行x,y軸方向變換時的原點就是它的錨點,上面所有圖的錨點都是左下角,這也是cocos2d-x默認的錨點位置。

接下來我們開始研究這三個變量的作用。為什么會有兩個錨點的變量來描述這個屬性呢?看_anchorPoint 的注釋有一名 Not in points 說明這不是描述點的。不能理解,那么我們看一下錨點的get set方法找找線索。

 

const Point& Node::getAnchorPointInPoints() const
{
    return _anchorPointInPoints;
}

/// anchorPoint getter
const Point& Node::getAnchorPoint() const
{
    return _anchorPoint;
}

void Node::setAnchorPoint(const Point& point)
{
#if CC_USE_PHYSICS
    if (_physicsBody != nullptr && !point.equals(Point::ANCHOR_MIDDLE))
    {
        CCLOG("Node warning: This node has a physics body, the anchor must be in the middle, you cann't change this to other value.");
        return;
    }
#endif
    
    if( ! point.equals(_anchorPoint))
    {
        _anchorPoint = point;
        _anchorPointInPoints = Point(_contentSize.width * _anchorPoint.x, _contentSize.height * _anchorPoint.y );
        _transformUpdated = _transformDirty = _inverseDirty = true;
    }
}

 

 

 

這兩個錨點的變量有兩個get方法和一個set方法。兩個get方法是分別得到這兩個變量的,不用多說了,看set方法

set方法接收一個參數point 並且接收的這個參數后面直接賦值給了 _anchorPoint 這個變量,那么也就是說這個set方法可以直接設置 _anchorPoint的值。

在這個方法里面還出現了一個成員變量 _contentSize 顧名思義這個變量描述了兩個 note對象的長寬大小

在設置完_anchorPoint的值后 還有一行代碼來設置 _anchorPointInPoints變量的值,我們分析一下這行代碼

_anchorPointInPoints = Point(_contentSize.width * _anchorPoint.x, _contentSize.height * _anchorPoint.y );

_anchorPointInPoints的值是整體Node的大小乘以_anchorPoint的 x ,y 分量得到的,這說明什么?

很明顯,_anchorPointInPoints是具體點的坐標而 _anchorPoint是 錨點在node上x , y分量的比例,如果錨點在node上面那么 _anchorPoint的x,y分量取值范圍肯定是[0,1]這間的數值。默認就為0。

小結一下,如果設置node的錨點那么只有一個函數來實現 setAnchorPoint 參數只有一個,含義就是錨點在node上x,y分量的比例。 舉個例子,如果要將錨點設置到node的中心只要這一段函數 setAchorPoint( Point(0.5f,0.5f)); 就可以了。

_contentSize的get ,set方法我們就略過了, 值得注意一點 setContentSize 方法中,當改變了_contentSize之后還做了一個操作就是重新更新了錨點的位置也就是重新計算了_anchorPointInPoints這個變量。

這里面出現了一個新碰到的數據類型就是Size,我們簡單看一下它的定義

 

class CC_DLL Size
{
public:
    float width;
    float height;

 

 

 

這個類只有兩個成員函數 寬度和長度,來描述一個方形區域的大小尺寸。

接下來的node類成員變量

kmMat4 _modelViewTransform; ///< ModelView transform of the 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;                         ///可以給當前的Node對象定義一個 int類型的標識
    
    std::string _name;               ///可以給當前Node對象起一個字符串類型的名稱。

 

 

 

這幾個變量說明Node是一個樹的數據結構,除非是根否則每個node對象都有父結點, _parent,每個Node還有一系列子結點 _children 這些子結點用了一個 Vector來存放,並且每個Node對象都有同級子結點(同一個父親的子結點)的一個Z軸方向的順序_localZOrder,在在游戲開發中有一個名詞叫做深度排序,類似這個意思。

還有一個變量_globalZOrder這是全局的一個深度排序。

通過這幾個成員變量,我們可以了解到,node對象上還可以嵌套Node對象,並且這些嵌套的node對象都是當前node的了結點。

查了一下這幾個變量相關的方法下面列一下咱們一個一個的分析。

先看向Node對象中增加子對象的方法,這里有三個重載版本的 addChild

首先看下面版本的addChild方法

 

void Node::addChild(Node *child, int zOrder, int tag)
{    
    CCASSERT( child != nullptr, "Argument must be non-nil");
    CCASSERT( child->_parent == nullptr, "child already added. It can't be added again");

    if (_children.empty())
    {
        this->childrenAlloc();
    }

    this->insertChild(child, zOrder);
    
#if CC_USE_PHYSICS
    if (child->getPhysicsBody() != nullptr)
    {
        child->getPhysicsBody()->setPosition(this->convertToWorldSpace(child->getPosition()));
    }
    
    for (Node* node = this->getParent(); node != nullptr; node = node->getParent())
    {
        if (dynamic_cast<Scene*>(node) != nullptr)
        {
            (dynamic_cast<Scene*>(node))->addChildToPhysicsWorld(child);
            break;
        }
    }
#endif

    child->_tag = tag;

    child->setParent(this);
    child->setOrderOfArrival(s_globalOrderOfArrival++);

    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();
    }
}

 

 

 

這里接收了三個參數,子child指針 ,zorder順序,標識tagID;

先判斷了當前結點是的子結點列表是否為空,如果 是空調用了Node類的成員函數 childrenAlloc(); 分配了內存。

然后調用了

this->insertChild(child, zOrder);

這個函數是根據zOrder的順序將child加入到當前結點的子結點列表里面,我們跟進這個函數看看是怎么實現的。
void Node::insertChild(Node* child, int z)
{
    _reorderChildDirty = true;
    _children.pushBack(child);
    child->_setLocalZOrder(z);
}

這個insertChild並沒有將 _children這個vecort的順序重新排列,只是將child這個對象加到了_children 列表的最后面。還做了改child的zOrder的設置操作。

注意到這里使用了_reorderChildDirty 這個變量,命名上理解,這個變量是標記着,當前Node對象的子對象列表是否需要重新排列操作,這里的設計很巧妙,在insertChild的時候不必排列一遍結點順序,小魚猜,肯定在使用這個結點的時候會根據_reorderChildDirty 這個變量的值來決定這個結點是否需要排列一遍子結點的順序。於是我在Node類定義中找到了這樣的一個方法。

void Node::sortAllChildren()
{
    if( _reorderChildDirty ) {
        std::sort( std::begin(_children), std::end(_children), nodeComparisonLess );
        _reorderChildDirty = false;
    }
}

這個方法是根據子結點的zorder順序排列所有的子結點

接着看addchild后面的操作,涉及到 CC_USE_PHYSICS 部分的我們先略過,只要知道Node也有支持物理引擎就可以了。

child->_tag = tag; 設置tag值

child->setParent(this); 將新加入的child結點的父結點重新定向為當前結點。

child->setOrderOfArrival(s_globalOrderOfArrival++);這個函數要注意一下, 在這里出現了一個Node類的靜態變量 s_globalOrderofArrival 這個變量是標記着系統創建Node對象的總數,在后面的方法中,只對這個變量做了自增的操作,刪除Node結點對其值沒有影響。

這個s_globalOrderOfArrival++除了記錄創建Node對象的總數外還有什么作用呢?

跟進 setorderOfArrival方法

void Node::setOrderOfArrival(int orderOfArrival)
{
    CCASSERT(orderOfArrival >=0, "Invalid orderOfArrival");
    _orderOfArrival = orderOfArrival;
}

我們可以看到,每個node是用 _orderofArrival來記錄它的全局創建的globalId值(s_globalOrderOfArrival) 的。

我們再看 _orderOfArrival的聲明。

int _orderOfArrival;            ///< used to preserve sequence while sorting children with the same localZOrder

看到這個變量的注釋,一切都明白了,這個s_globalOrderofArrival值其實就是給每個Node一個創建時候的全局ID,主要的用途是在做Node z軸深度排序的時候如果兩個結點的 localZorder值一樣,那么就以_orderofArrival值來做排序。

 

再看這里面有一個_running的成員變量,這個變量是標記這Node結點是否處於運行狀態的,至於什么是運行狀態,看到這里還不是很明確,只要知道就是一個狀態就可以了。

在running狀態下,新加入的子結點會調用一個 onEnter函數,前面提到過,Node類定義了一些事件,這個onEnter就是其中的一個事件,從代碼上直接可以理解這個onEnter事件的含義是,當結點被加入到正在運行的結點后就會觸發的一個回調函數。

下面還有一個回調事件

// prevent onEnterTransitionDidFinish to be called twice when a node is added in onEnter
        if (_isTransitionFinished) {
            child->onEnterTransitionDidFinish();
        }

從命名上理解,_isTransitionfinished 是一個標記着Node對象是否做完了變換的一個狀態,這里觸發了事件的回調函數 onEnterTransitionDidFinish

在addChild聲明上有一句注釋,如果child加到一個running的node里面那么 onEnter和onEnterTransitionDidFinish會被立即調用。

在addChild的最后又出現兩個狀態變量和兩個函數

    if (_cascadeColorEnabled)
    {
        updateCascadeColor();
    }
    
    if (_cascadeOpacityEnabled)
    {
        updateCascadeOpacity();
    }

這兩個變量是標記是否繼承父結點的顏色與透明度如果父結點定義了要子結點繼承它的顏色與透明設置那么會遞歸的更新每個子結點的顏色與透明度與父結點一樣。

到此,這個addChild方法我們已經看明白了,關於物理方面的東西先放到一邊,從后章節我們來分析。

再看看其它兩個addChild的重載方法

 
void Node::addChild(Node *child, int zOrder)
{
    CCASSERT( child != nullptr, "Argument must be non-nil");
    this->addChild(child, zOrder, child->_tag);
}

void Node::addChild(Node *child)
{
    CCASSERT( child != nullptr, "Argument must be non-nil");
    this->addChild(child, child->_localZOrder, child->_tag);
}

這兩個方法很簡單,里面主要都是調用了上面我們分析的三個參數的重載addchild方法。

參數中如果沒有指定 zOrder則將結點加入到 0 這個位置。

再看幾個關於child操作的get方法

/**
     * 通過tag得到child指針     *
     * @param tag   An identifier to find the child node.
     *
     * @return a Node object whose tag equals to the input parameter
     */
    virtual Node * getChildByTag(int tag);
    /**
     * 返回_children的引用,與const引用     *
     * @return the array the node's children
     */
    virtual Vector<Node*>& getChildren() { return _children; }
    virtual const Vector<Node*>& getChildren() const { return _children; }
    
    /** 
     * 返回子結點的數量
     *
     * @return The amount of children.
     */
    virtual ssize_t getChildrenCount() const;

還有一些關於parent的操作,這里代碼很簡單,看一眼就了解了。

還有一些刪除結點的操作,這里我貼一下代碼,把說明寫到注釋中,大家理解一下就可以了。難度不大。

   /**
     * 將當前結點從它的父結點中清除,並且調用了clean方法做了清除,clean方法是什么后面我們有分析。.
     */
    virtual void removeFromParent();
    /** 同上面的removeFromParent方法,有一個參數可以指定是否調用 clean方法做清除
     */
    virtual void removeFromParentAndCleanup(bool cleanup);

    /**
     * 通過 child的指針來刪除一個指定的結點
     */
    virtual void removeChild(Node* child, bool cleanup = true);

    /**
     * 通過tag值來刪除一個子結點
     */
    virtual void removeChildByTag(int tag, bool cleanup = true);
    /**
     * 清除所有的子結點並且所有的子結點都會調用 cleanup 方法 
     */
    virtual void removeAllChildren();
    /**
     * 清除所有子結點,根據cleanup的參數設置來決定是否調用cleanup方法 (這里出現了多次cleanup這個函數后面我們重點分析一下源碼)
     */
    virtual void removeAllChildrenWithCleanup(bool cleanup);

    /**
     * 這個函數干了幾件事,
     * 1. 設置了_reorderChildDirty = true 設置當前結點的子結點進行排序
     * 2. 重新更新了child的 _orderOfArrival 值為最新的
     * 3. 設置 child的 _localZOrder值為 localZOrder 
     */
    virtual void reorderChild(Node * child, int localZOrder);

    /**
     * 根據 子結點的的 _localZOrder排列當前結點所有的child ,有了這個方法不用每加一個結點或者改變某個子結點的zOrder就排一次,這個排列方法要在結點顯示到屏幕之前調用。
*/
    virtual void sortAllChildren();

    /**
     * tag的get set 方法 
     */
    virtual int getTag() const;
    virtual void setTag(int tag);

上面幾個方法都很簡單,大家可以結合代碼還有我的解釋來理解。

這里還有一個變量不得不提及一下

// XXX: Yes, nodes might have a sort problem once every 15 days if the game runs at 60 FPS and each frame sprites are reordered.
int Node::s_globalOrderOfArrival = 1;

還是這個結點的全局計數,在 reorderChild方法中,會給子結點更新s_globalOrderOfArrival值,而globalOrderOfArrival值是一個只增不減的變量,還是個int類型的,所以globalOrderOfArrival值會有被用完的時候,這個變量在定義的時候有注釋說明了這一點。

如果程序以每秒60幀,每幀都調用reordered的速率運行15天,Node結點排序就會出現問題。

這里確實有這樣的問題,不過大家也不用擔心,又不是服務器程序,連續15天不停的運行基本不會發生這樣的情況的,程序重新啟動這個變量又從1開始了。

 

上面幾個child的操作中多次提及到了 cleanup(); 操作,下面我們來分析下cleanup的源碼。

 

void Node::cleanup()
{
    // actions
    this->stopAllActions();
    this->unscheduleAllSelectors();
    
#if CC_ENABLE_SCRIPT_BINDING
    if ( _scriptType != kScriptTypeNone)
    {
        int action = kNodeOnCleanup;
        BasicScriptData data(this,(void*)&action);
        ScriptEvent scriptEvent(kNodeEvent,(void*)&data);
        ScriptEngineManager::getInstance()->getScriptEngine()->sendEvent(&scriptEvent);
    }
#endif // #if CC_ENABLE_SCRIPT_BINDING
    
    // timers
    for( const auto &child: _children)
        child->cleanup();
}

 

這個cleanup其實就干了四件事

  1. 停止了所有的動作
  2. 停止了定時器的調度(具體這個schedule是個什么玩意我們后面專門章節分析)
  3. 傳遞給腳本停止事件如( lua腳本 )
  4. 遞歸每個子結點調用cleanup方法。

這就是清理工作。

現在回到我們分析Node類的主線主,繼續看Node類的成員變量,在上面代碼分析中部分成員變量已經做了分析下面就不再贅述。

    void *_userData;                ///< A user assingned void pointer, Can be point to any cpp object
    Ref *_userObject;               ///< A user assigned Object

通過注釋,和命名分析,這再從個函數是Node類給開發者預留的再從個數據接口,說白一點就是,如果你覺得Node類哪些功能還不夠用,或者它與哪些對象要進行綁定,在不使用繼承的方式下,可以將你自定義的數據掛靠在Node類的 _userData或 _userObject指針上面。

針對這再從個變量的get set方法很簡單,大家自行看下代碼。

 

    GLProgram *_shaderProgram;      ///< OpenGL shader 
    Scheduler *_scheduler;          ///< scheduler used to schedule timers and updates

    ActionManager *_actionManager;  ///< a pointer to ActionManager singleton, which is used to handle all the actions

    EventDispatcher* _eventDispatcher;  ///< event dispatcher used to dispatch all kinds of events

這幾個成員變量,從命名上就可以了解它的作用,

OpenGl的shader控制對象

Scheduler 調度程序控制

ActionManager 動作管理器

EventDispatcher 事件分發器

這些對象的具體工作原理我們在這里不做深入討論,只要知道Node類偶合了這些東西就可以了。

還有一個很重要的Node類的屬性

bool _visible;                  ///< is this node visible

描述結點是否可見,如果隱藏一個結點的顯示可以調用關於這個變量的get set方法,判斷一個結點是否被顯示 可以調用 isVisible方法來查詢。

最后幾個是來描述結點的透明屬性的方法,這里不多說大家了解一下就可以了。

// opacity controls
    GLubyte        _displayedOpacity;
    GLubyte     _realOpacity;
    Color3B        _displayedColor;
    Color3B     _realColor;

至此,我們已經完整的了解 了Node類的所有屬性,基本明白了Node是個什么玩意,它的地位肯定是Cocos2d-x里很重要的,因為它是就是用來操作顯示對象的一個基類。

成員變量看完了,我們來看一下Node類提供的方法都有哪些,上面分析成員變量的時候已經分析了大部分的方法,下面我們看看剩下的方法都用來做什么的。

先從Node結點的創建方法函數開始。

Node::Node(void)
: _rotationX(0.0f)
, _rotationY(0.0f)
, _rotationZ_X(0.0f)
, _rotationZ_Y(0.0f)
, _scaleX(1.0f)
, _scaleY(1.0f)
, _scaleZ(1.0f)
, _positionZ(0.0f)
, _position(Point::ZERO)
, _skewX(0.0f)
, _skewY(0.0f)
, _anchorPointInPoints(Point::ZERO)
, _anchorPoint(Point::ZERO)
, _contentSize(Size::ZERO)
, _useAdditionalTransform(false)
, _transformDirty(true)
, _inverseDirty(true)
, _transformUpdated(true)
// children (lazy allocs)
// lazy alloc
, _localZOrder(0)
, _globalZOrder(0)
, _parent(nullptr)
// "whole screen" objects. like Scenes and Layers, should set _ignoreAnchorPointForPosition to true
, _tag(Node::INVALID_TAG)
// userData is always inited as nil
, _userData(nullptr)
, _userObject(nullptr)
, _shaderProgram(nullptr)
, _orderOfArrival(0)
, _running(false)
, _visible(true)
, _ignoreAnchorPointForPosition(false)
, _reorderChildDirty(false)
, _isTransitionFinished(false)
#if CC_ENABLE_SCRIPT_BINDING
, _updateScriptHandler(0)
#endif
, _componentContainer(nullptr)
#if CC_USE_PHYSICS
, _physicsBody(nullptr)
#endif
, _displayedOpacity(255)
, _realOpacity(255)
, _displayedColor(Color3B::WHITE)
, _realColor(Color3B::WHITE)
, _cascadeColorEnabled(false)
, _cascadeOpacityEnabled(false)
{
    // set default scheduler and actionManager
    Director *director = Director::getInstance();
    _actionManager = director->getActionManager();
    _actionManager->retain();
    _scheduler = director->getScheduler();
    _scheduler->retain();
    _eventDispatcher = director->getEventDispatcher();
    _eventDispatcher->retain();
    
#if CC_ENABLE_SCRIPT_BINDING
    ScriptEngineProtocol* engine = ScriptEngineManager::getInstance()->getScriptEngine();
    _scriptType = engine != nullptr ? engine->getScriptType() : kScriptTypeNone;
#endif
    
    kmMat4Identity(&_transform);
    kmMat4Identity(&_inverse);
    kmMat4Identity(&_additionalTransform);
}

構造函數

1. 初始化列表里一些變量的默認值,簡單過一眼,后面拿到了 Director實例

2 . 將Director的 動作管理器,定時器,事件分發器的指針都賦值給了這個Node結點並且增加了一次引用 計數。

3. 腳本相關變量的初始化

4. 初始化了一些矩陣(現在可以不了解這些矩陣的具體用處,只知道有這么一回事就行了)。

析構函數

Node::~Node()
{
    CCLOGINFO( "deallocing Node: %p - tag: %i", this, _tag );
    
#if CC_ENABLE_SCRIPT_BINDING
    if (_updateScriptHandler)
    {
        ScriptEngineManager::getInstance()->getScriptEngine()->removeScriptHandler(_updateScriptHandler);
    }
#endif

    // User object has to be released before others, since userObject may have a weak reference of this node
    // It may invoke `node->stopAllAction();` while `_actionManager` is null if the next line is after `CC_SAFE_RELEASE_NULL(_actionManager)`.
    CC_SAFE_RELEASE_NULL(_userObject);
    
    // attributes
    CC_SAFE_RELEASE_NULL(_shaderProgram);

    for (auto& child : _children)
    {
        child->_parent = nullptr;
    }

    removeAllComponents();
    
    CC_SAFE_DELETE(_componentContainer);
    
#if CC_USE_PHYSICS
    setPhysicsBody(nullptr);

#endif
    
    CC_SAFE_RELEASE_NULL(_actionManager);
    CC_SAFE_RELEASE_NULL(_scheduler);
    
    _eventDispatcher->removeEventListenersForTarget(this);
    
#if CC_NODE_DEBUG_VERIFY_EVENT_LISTENERS && COCOS2D_DEBUG > 0
    _eventDispatcher->debugCheckNodeHasNoEventListenersOnDestruction(this);
#endif

    CCASSERT(!_running, "Node still marked as running on node destruction! Was base class onExit() called in derived class onExit() implementations?");
    CC_SAFE_RELEASE(_eventDispatcher);
}

大家可以仔細看一下,析構中,清理了一些指針如 _userObject、_shaderprogram、

還清理了components 這個components是什么呢,我們先只了解到命名程序,后續章節來單獨分析它,這里只知道清理了Node的組件就可以了。

在做這些指針的清理工作時用到了一些宏定義 如 CC_SAFE_RELEASE_NULL , CC_SAFE_RELEASE 等,大家跟一下代碼,其實沒什么深奧的就是判斷指針是不是空,不空就做 release或者 free 等操作。

值得提及的是在Node的類定義文件中有一行代碼。

private:
    CC_DISALLOW_COPY_AND_ASSIGN(Node);

這個宏定義是將Node類的拷貝構造函數和賦值運算符重載定義成了私有方法。

這說明,在針對Node操作的時候不能進行賦值運算,Node做參數的時候要以指針的形式來傳遞參數。

下面我們看一下Node類最重要的一個創建對象方法create方法

Node * Node::create(void)
{
    Node * ret = new Node();
    if (ret && ret->init())
    {
        ret->autorelease();
    }
    else
    {
        CC_SAFE_DELETE(ret);
    }
    return ret;
}

這個create方法很簡單,

1. new一個Node對象
2. 調用Node的init方法進行初始化。

3. 設置Node 為自動內存釋放。

Cocos2d-x推薦我們作用create方法來創建結點,而不要用new的方式創建。我們在應用的時候應該尊重這條建議。

create方法里面提供到了一個init方法,下面我們跟進源碼,看一下init都干了些什么。

virtual bool init();
bool Node::init()
{
    return true;
}

看到了這么干凈的init方法,里面就一行代碼。怎么回事呢?

我們看一下init方法的聲明為 virtual 所以這個方法是給它的子類使用的,不同的子類有不同的init過程 所以這里只是預留了一個初始化的接口,在你的類中如果有自己的特殊的初始化過程那么重載這個init就可以了。

Node的創建方法我們就分析到這里,下面我們繼續看Node類有哪些方法還沒有查看過。

/**
     * Override this method to draw your own node.
     * The following GL states will be enabled by default:
     * - `glEnableClientState(GL_VERTEX_ARRAY);`
     * - `glEnableClientState(GL_COLOR_ARRAY);`
     * - `glEnableClientState(GL_TEXTURE_COORD_ARRAY);`
     * - `glEnable(GL_TEXTURE_2D);`
     * AND YOU SHOULD NOT DISABLE THEM AFTER DRAWING YOUR NODE
     * But if you enable any other GL state, you should disable it after drawing your node.
     */
    virtual void draw(Renderer *renderer, const kmMat4& transform, bool transformUpdated);
    virtual void draw() final;

我們看到了有再從個重載的draw方法, 大家 跟一下代碼可以看到這兩個方法基本是一個空的實現,注釋上已經說的很清楚了,在你自己的node實現中重載第一個方法來用OpenGL繪制自己的node

第二個不帶參數的方法是不能被重載的,大家注意一下這一點.

接下來是兩個visit方法

/**
     * Visits this node's children and draw them recursively.
     */
    virtual void visit(Renderer *renderer, const kmMat4& parentTransform, bool parentTransformUpdated);
    virtual void visit() final;

和上面draw的定義結構很像,我們看一個帶參數的visit的定義 .

void Node::visit(Renderer* renderer, const kmMat4 &parentTransform, bool parentTransformUpdated)
{
    // quick return if not visible. children won't be drawn.
    if (!_visible)
    {
        return;
    }

    bool dirty = _transformUpdated || parentTransformUpdated;
    if(dirty)
        _modelViewTransform = this->transform(parentTransform);
    _transformUpdated = false;


    // IMPORTANT:
    // To ease the migration to v3.0, we still support the kmGL stack,
    // but it is deprecated and your code should not rely on it
    kmGLPushMatrix();
    kmGLLoadMatrix(&_modelViewTransform);

    int i = 0;

    if(!_children.empty())
    {
        sortAllChildren();
        // draw children zOrder < 0
        for( ; i < _children.size(); i++ )
        {
            auto node = _children.at(i);

            if ( node && node->_localZOrder < 0 )
                node->visit(renderer, _modelViewTransform, dirty);
            else
                break;
        }
        // self draw
        this->draw(renderer, _modelViewTransform, dirty);

        for(auto it=_children.cbegin()+i; it != _children.cend(); ++it)
            (*it)->visit(renderer, _modelViewTransform, dirty);
    }
    else
    {
        this->draw(renderer, _modelViewTransform, dirty);
    }

    // reset for next frame
    _orderOfArrival = 0;
 
    kmGLPopMatrix();
}

這里面代碼還挺多,我們簡單分析一下其實就干了這么幾件事

  1. 判斷當前結點的visible屬性,即當前結點是否可見,如果不可見直接返回。
  2. 根據當前結點和父結點的矩陣變化 狀態標記來判斷當前結點是否要做一些矩陣操作。
  3. 對其子結點進行了排序
  4. 調用 localZorder < 0 的子結點的visit
  5. 繪制自己調用 了 draw
  6. 繪制_localZorder>0的那些子結點
  7. 設置_orderOfArrival=0; 這塊代碼為什么要重置 _orderOfArrival呢? 小魚理解,在visit一個結點后這個結點肯定就會在同級結點中排序過,所以_orderOfArrival值也就沒有用了,置為0 ,所有這個值為0的都是已經被排序過的子結點。

小結一下:

node結點具體怎么來渲染使用的是 draw方法,

如果要顯示一個node結點及其子結點那么我們就要使用visit方法,這樣可以遞歸的按照zOrder順序地draw每一個子結點。

下來還有一些與定時器有關的方法,這些方法的具體使用及功能實現,我們在單獨分析schedule類的時候再合並討論。

還有一些小的功能函數,我貼一下源碼,大家簡單做了 解

void Node::resume()
{
    _scheduler->resumeTarget(this);
    _actionManager->resumeTarget(this);
    _eventDispatcher->resumeEventListenersForTarget(this);
}

void Node::pause()
{
    _scheduler->pauseTarget(this);
    _actionManager->pauseTarget(this);
    _eventDispatcher->pauseEventListenersForTarget(this);
}

暫停與恢復暫停

從函數過程看,暫停主要是暫時暫停了 計時器,動作,事件,在手機游戲 進入到后台運行的時候,我們會調用 這些方法。

 

好,到這里,我們把Node類的源碼基本分析完了,在這里小魚做一個小小的總結,幫大家回顧一下上面都啰嗦了些什么。

  1. Node 類在Cocos2d-x里地位顯赫,它是顯示對象(在手機等設備里顯示的東西)的基類。
  2. Node類是一個樹狀結構的一個結點,它最多只有一個父結點,可以有多個子結點,這些子結點存放在一個vector的數據結構中
  3. 創建Node對象不要使用new方法,而使用create方法。
  4. Node類的內存管理是走的autorelease機制的,所以不要delete對象而是采用Cocos2d-x的引用計數機制來釋放對象。
  5. 向node結點中加子結點時用addchild方法(這里如果忘記了addchild都干了些什么可以翻到上面回顧一下)
  6. 將node結點顯示到屏幕上使用visit方法,在visit方法里面會調用draw來繪制node.我們在使用的時候可以重載draw方法這樣就可以讓node按自己的方式來顯示出來。
  7. node還支持名稱,tag(id)等標識,可以通過這些標識來查找node的指針。
  8. Node還支持一些旋轉,變形,綻放等操作,並且這些設置子結點也會繼承的。
  9. Node里面還引用了 定時器,事件分發,動作管理器等。
  10. Node有一些自己的事件,當被加入到入結點中會調用 enter當被移出父結點時會觸發 exit

好啦,node類我們分析到這,不過在本章節開始,在我們看ccnode.h文件時,說過這個文件有兩個類,現在我們看一下另一個類是個什么東西。

class CC_DLL __NodeRGBA : public Node, public __RGBAProtocol
{
public:
    // overrides
    virtual GLubyte getOpacity() const override { return Node::getOpacity(); }
    virtual GLubyte getDisplayedOpacity() const  override { return Node::getDisplayedOpacity(); }
    virtual void setOpacity(GLubyte opacity) override { return Node::setOpacity(opacity); }
    virtual void updateDisplayedOpacity(GLubyte parentOpacity) override { return Node::updateDisplayedOpacity(parentOpacity); }
    virtual bool isCascadeOpacityEnabled() const  override { return Node::isCascadeOpacityEnabled(); }
    virtual void setCascadeOpacityEnabled(bool cascadeOpacityEnabled) override { return Node::setCascadeOpacityEnabled(cascadeOpacityEnabled); }

    virtual const Color3B& getColor(void) const override { return Node::getColor(); }
    virtual const Color3B& getDisplayedColor() const override { return Node::getDisplayedColor(); }
    virtual void setColor(const Color3B& color) override { return Node::setColor(color); }
    virtual void updateDisplayedColor(const Color3B& parentColor) override { return Node::updateDisplayedColor(parentColor); }
    virtual bool isCascadeColorEnabled() const override { return Node::isCascadeColorEnabled(); }
    virtual void setCascadeColorEnabled(bool cascadeColorEnabled) override { return Node::setCascadeColorEnabled(cascadeColorEnabled); }

    virtual void setOpacityModifyRGB(bool bValue) override { return Node::setOpacityModifyRGB(bValue); }
    virtual bool isOpacityModifyRGB() const override { return Node::isOpacityModifyRGB(); }

protected:
    __NodeRGBA();
    virtual ~__NodeRGBA() {}

private:
    CC_DISALLOW_COPY_AND_ASSIGN(__NodeRGBA);
};

這個類是一個__NodeRGBA類,從命名上看是帶RGB顏色的Node類,里面還繼承了一個抽象類,__RGBAProtocol

大家可以看一下__RGBAProtocol這個類,里面全是純虛函數,也就是一個接口類,而__NodeRGBA類實現的這些接口其實都是調用 了Node基類的方法。

我們又看到__NodeRGBA的構造函數是 protected保護模式的,又沒有提供create方法來創建這個類的對象,通過這些我們可以分析到,現在這個版本的node類已經集成了__NodeRGBA了。

我在整個工程里面搜索了一下 __NodeRGBA 基本沒有一 個地方用到過這個類,這樣我們可以放心 的不去理會__NodeRGBA這個類了,它已經沒有用了。

今天這章節有點長啊!!因為Node類確實是一個重量級的人物,我們分析了它的源碼,涉及到 物理,實時器,動作,矩陣方面的內容小魚暫時一筆帶過,因為在看源碼的時候 切忌一根筋的往里看,要使用廣度優先的方式,從全局的角度來分析。這些內容我們暫時只要知道是干什么的,具體怎么干,等有了大局觀再逐個擊破。

我們不着急看Node類的一些重要的子類如 Scene Layer等,下一節,我們來分析 Scheduler 類,了解一下Cocos2d-x的定時器調度是怎么實現的,它怎么使用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM