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

    上一章 咱們分析了Cocos2d-x的內存管理,主要解剖了 Ref、PoolManager、AutoreleasePool這三個類,瞭解了對象是如何自動釋放的機制。以前有一個類 Node常常出如今各類場合,不是作爲參數就是作爲返回值,那麼這一章節咱們就去看看這個Node類到底在Cocos2d-x裏處於一個什麼樣的地位。node

    直接進入主題,咱們打開CCNode.h文件。我去,這個文件有1500行,這麼長怎麼看啊,放鬆一下總體看過一遍,鬆了一口氣,還好,還沒那麼糟,文件雖然大,註釋佔了有90%的篇幅,代碼很少,註釋多正好 方便咱們閱讀,信心來了。服務器

    CCNode.h文件裏面一共定義了兩個類 Node與__NodeRGBA類,從命名上能夠看得出 __NodeRGBA類確定是依靠Node類,或者說是Node類的一個擴展,那麼好咱們開始看一下今天的主角 Node。數據結構

    老規矩,在看Node類以前咱們先看一下這個文件裏面的頭文件和引用的類都有哪些,經過這些信息能讓咱們更好的瞭解Node類和其它類的關係。ide

 

#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 動做相關ui

LabelProtocol 標籤UI接口this

Scheduler 調度控制lua

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的定時器調度是怎麼實現的,它怎麼使用。

相關文章
相關標籤/搜索