Cocos2d-X3.0 刨根问底(五)----- Node类及显示对象列表源码分析
转自:http://www.cnblogs.com/mmidd/p/3761290.html
上一章 我们分析了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.hCCGL.hccGLStateCache.hCCGLProgram.hCCScriptSupport.hCCProtocols.hCCEventDispatcher.hCCVector.hkazmath/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类是个重要的角色啊 下面简要分析一下引入的类
GridBase、Point自己定义 的数据类型,格子与点
Touch应该和手机触摸事件相关的
Action动作相关
LabelProtocol标签UI接口
Scheduler调度控制
ActionManager动作管理器 (动作这部分后面我们专门的章节来讨论)
Component组件(是什么组件呢?不看代码不知道啊,之后碰到了再分析源码)
ComponentContainer(组件容器,貌似和UI有关系)
EventDispatcher、EventListener这是事件相关的(事件我们也分章节讨论)
Scene场景
Renderer渲染相关
还定义了几个事件,用了一个枚举 kNodeOnEnter kNodeOnExit kNodeOnEnterTransitionDidFinish kNodeOnExitTransitionDidStart kNodeOnCleanup
boolnodeComparisonLess(Node* n1,Node*n2); 这个函数看起来是一个谓词函数用来比较两个Node类的大小
到目前为止,Node类涉及到的元素我们也有了初步了解,接下来我们看一下Node类的定义
classCC_DLL Node :publicRef
Node类继承了Ref类,采用了Cocos2d-x的内部内存管理机制,这个上一章节我们讨论过不再详述。
再看一下Node类所有成员变量,看看究竟Node都封装了哪些东东。
类定义最先定义了一个类静态变量值得我们注意一下。
staticconstintINVALID_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,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,255); line-height:1.5!important">float
_z) : x(_x),y(_y),z(_z) {} Vertex3F(): x(0.f),y(0
.f) {}
GLfloat x;
GLfloat y;
GLfloat z;
};
Vertex3F就是一个三维数据结构体,没什么深奥的。
为了理解好RotationX,RotationY 是什么样的属性,小鱼做了一个小小的Demo帮助一起理解,这里放出Gif图。
_rotationX 每100毫秒增加10度 呈现效果
_rotationY 每100毫秒增加10度 呈现效果
处于好奇,如果rotationX Y一起变化是个什么情况呢?
_rotationX _rotationY 每100毫秒各增加10度 呈现效果
哈哈,通过这几个效果一目了然这两个变量所起的作用了。
下面继续看,第二组Node的成员变量
//rotation Z is decomposed in 2 to simulate Skew for Flash animations
float_rotationZ_X;
< rotation angle on Z-axis,component X
_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.
void setRotation(float rotation);
*
* Returns the rotation of the node in degrees.
*
* @see `setRotation(float)`
*
* @return The rotation of the node in degrees.
float getRotation()
这两个方法是同时设置/获得 _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.
void setRotationSkewX(
float rotationX);
CC_DEPRECATED_ATTRIBUTE 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.
float getRotationSkewX()
const;
CC_DEPRECATED_ATTRIBUTE 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.
void setRotationSkewY(
float rotationY);
CC_DEPRECATED_ATTRIBUTE 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.
float getRotationSkewY()
float getRotationY()
return getRotationSkewY(); }
英文水平好的同学可以看看注释加以理解
像小鱼这样英语四级考了八次都没过的,可以参考下面的gif图来理解这两个变量究竟是来做什么的。
_rotationZ_X 每100毫秒增加10度 呈现效果
_rotationZ_Y 每100毫秒增加10度 呈现效果
_rotationZ_X _rotationZ_Y一起改变 呈现效果
怎么样,当_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
_scaleY;< scaling factor on y-axis
_scaleZ;< scaling factor on z-axis
这个不用解释太多,就是延 x,y,z轴的缩放比例
看一下对应这三个值的get/set方法
*
* _scaleX的 get set
方法
void setScaleX(
float scaleX);
float getScaleX()
const;
*
* _scaleY的 get set 方法
void setScaleY(
float scaleY);
float getScaleY()
const;
*
* _scaleZ的get set 方法
void setScaleZ(
float scaleZ);
float getScaleZ()
*
* 将_scaleX _scaleY _scaleZ设置成相同值的 get set 方法
void setScale(
float scale);
float getScale()
下面我用一个图来说明这几个方法的作用
缩放比例 当 0~1时是缩小 等于1时是原大小 大于1时是放大。
通过上在的图可以看到实际上在z方向的缩放对Node实际上不起作用,很正常因为这是2D引擎。
继续看Node类的属性
Point _position;< node的位置
_positionZ;< 在OpenGl里的 z位置
node的这两个属性一看就知道是什么意思了,就是标记这个node的位置的变量,这两个变量的 get set 方法很简单就不跟进了。
这里出现了一个新的类型 Point 从命名上就可以知道这是记录一个点的数据结构
我们看一下这个Point类是怎么定义的
class
CC_DLL Point {public
:float
x;floaty;
这个类后面有一大堆的方法和运算符重载操作,有兴趣的同学可以跟进看一下,大概就是操作这 x,y两个值 的,有比较两个point的方法求平方和,点乘,叉乘,等向量运算的方法,可以把它理解成一个点,也可以理解成一个向量,具体看实际应用。
再继续向下看Node的成员变量
float_skewX;< skew angle on x-axis
_skewY;< skew angle on y-axis
又出来了两个形变的变量,从字面上理解是与x,y 轴的角度
再跟进这个个变量的 get set 方法很简单,就是普通的赋值与返回值。
我再用图例来学习这个变量会影响到Node的什么属性。
_skewX 每100毫秒增加10度 呈现效果
_skewY 每100毫秒增加10度 呈现效果
再看一下 skewX与 skewY一同变化的效果
不用多说,仔细观察这些变化 就可以知道这两个参数的作用了。
接下来是两个有关锚点的成员变量
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()
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,255); line-height:1.5!important">int zOrder,255); line-height:1.5!important">int
tag)
{
CCASSERT( child != nullptr,
Argument must be non-nil");
CCASSERT( child->_parent == nullptr,0); line-height:1.5!important">child already added. It can't be added again
");
if (_children.empty())
{
this->
childrenAlloc();
}
this->
insertChild(child,zOrder);
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,255); line-height:1.5!important">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,0); line-height:1.5!important">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事件的含义是,当结点被加入到正在运行的结点后就会触发的一个回调函数。
下面还有一个回调事件
if (_isTransitionFinished) {
child->onEnterTransitionDidFinish();
}
从命名上理解,_isTransitionfinished 是一个标记着Node对象是否做完了变换的一个状态,这里触发了事件的回调函数onEnterTransitionDidFinish
在addChild声明上有一句注释,如果child加到一个running的node里面那么 onEnter和onEnterTransitionDidFinish会被立即调用。
在addChild的最后又出现两个状态变量和两个函数
if
(_cascadeColorEnabled)
{
updateCascadeColor();
}
if (_cascadeOpacityEnabled)
{
updateCascadeOpacity();
}
这两个变量是标记是否继承父结点的颜色与透明度如果父结点定义了要子结点继承它的颜色与透明设置那么会递归的更新每个子结点的颜色与透明度与父结点一样。
到此,这个addChild方法我们已经看明白了,关于物理方面的东西先放到一边,从后章节我们来分析。
再看看其它两个addChild的重载方法
int zOrder)
{
CCASSERT( child != nullptr,0); line-height:1.5!important">");
this->addChild(child,zOrder,child->_tag);
}
void Node::addChild(Node *child)
{
CCASSERT( child != nullptr,child->_localZOrder,child->_tag);
}