对于cocos2dx中的基类Node,一切都从这开始,Node这里值得分析的东西很多,看来是得花很长一个篇幅来讲。

我预计可以分3块东西讲

  1. 数学相关
  2. 渲染相关
  3. 引擎相关

路径

1
2d/base-node/CCNode.h
2d/base-node/CCNode.cpp

类图

先附上一张类图

Node子类

好长哦~~

源码分析

Node作为大部分子类的父类,可见是得好好分析分析。

先贴上构造函数,析构函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
Node::Node(void)
/* 此处略去构造函数初始化列表 */
{
Director *director = Director::getInstance();
/* 获取全局的动作管理类 */
_actionManager = director->getActionManager();
_actionManager->retain();

/* 获取全局的定时器 */
_scheduler = director->getScheduler();
_scheduler->retain();

/* 获取全局的事件分配器 */
_eventDispatcher = director->getEventDispatcher();
_eventDispatcher->retain();

/* 矩阵相关 */
kmMat4Identity(&_transform);
kmMat4Identity(&_inverse);
kmMat4Identity(&_additionalTransform);
}

Node::~Node()
{
/* userObject 必须在先被释放,或许有来自这个Node的弱引用 */
/* 这里先这样注释着,带我仔细研究一番,再来修改 */
CC_SAFE_RELEASE_NULL(_userObject);

/* 着色器相关 */
CC_SAFE_RELEASE_NULL(_shaderProgram);

/* 将子儿子的父节点置为nullptr */
for (auto& child : _children)
{
child->_parent = nullptr;
}

/* 不知此为何物,等会儿看看~ */
/* 数据驱动组件 */
/* 默认是没有启用,若不用ECS来开发游戏的话,木有用~~ */
removeAllComponents();
CC_SAFE_DELETE(_componentContainer);

#if CC_USE_PHYSICS
/* 3.x 之后封装的物理引擎,据说有挺多问题 */
setPhysicsBody(nullptr);
#endif

/* 将Node::Node()中retain的release */
CC_SAFE_RELEASE_NULL(_actionManager);
CC_SAFE_RELEASE_NULL(_scheduler);
_eventDispatcher->removeEventListenersForTarget(this);
CC_SAFE_RELEASE(_eventDispatcher);
}

待我去看看Components是毛

by etond 20141022 10:31


bingling~

我回来了,貌似传说中的数据驱动(Entiy-Component-System)的组件~

by etond 20141022 11:21

自然还是需要google一番才是

或许发现一个不错的网站 www.gamedev.net
下午好好研究这几篇文章之后再继续写

我还找到了一个挺不错的pptOGDC 2014: Component based entity system mobile game development

by etond 20141022 11:44

此块内容移步Entity Component System


终于从栈中递归出来了。(鼓掌~)

这里先看看Node的成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/* 位置,大小,缩放,扭曲,描点 */
float _rotationX;
float _rotationY;
float _rotationZ_X;
float _rotationZ_Y;
float _scaleX;
float _scaleY;
float _scaleZ;
Point _position;
float _positionZ;
float _skewX;
float _skewY;
/* 转换坐标用 */
Point _anchorPointInPoints;
Point _anchorPoint;
Size _contentSize;

/* 投影矩阵 */
kmMat4 _modelViewTransform;
/* 变换矩阵 */
mutable kmMat4 _transform;
mutable bool _transformDirty;
/* 逆矩阵,变换矩阵的逆矩阵 */
/* 作用就是撤销操作 */
mutable kmMat4 _inverse;
mutable bool _inverseDirty;
/* 自定义矩阵 */
/* 和CCAtlasNode相关 */
mutable kmMat4 _additionalTransform;
bool _useAdditionalTransform;
bool _transformUpdated;

int _localZOrder;
float _globalZOrder;
Vector<Node*> _children;
Node *_parent;
int _tag;
std::string _name;

/* 用户数据 */
void *_userData;
Ref *_userObject;

GLProgram *_shaderProgram;

int _orderOfArrival;

Scheduler *_scheduler;
ActionManager *_actionManager;
EventDispatcher* _eventDispatcher;
bool _running;
bool _visible;
bool _ignoreAnchorPointForPosition;
bool _reorderChildDirty;
bool _isTransitionFinished;

/* 脚本相关 */
int _scriptHandler;
int _updateScriptHandler;
ccScriptType _scriptType;

/* ECS */
ComponentContainer *_componentContainer;

/* 物理引擎 */
PhysicsBody* _physicsBody;

GLubyte _displayedOpacity;
GLubyte _realOpacity;
Color3B _displayedColor;
Color3B _realColor;
bool _cascadeColorEnabled;
bool _cascadeOpacityEnabled;

/* _orderOfArrival会慢慢递增,当_localZOrder相同时,按照这个来排序 */
static int s_globalOrderOfArrival;

(我赤裸裸的从源码里面拷贝了这些代码,并加上了邪恶的中文注释)

数学相关

先看看getNodeToParentTransform的实现,这个方法每一帧都会被调用,去获得最新的变换矩阵。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
const kmMat4& Node::getNodeToParentTransform() const
{
if (_transformDirty)
{
/* 位置 */
float x = _position.x;
float y = _position.y;
float z = _positionZ;

/* 根据描点重新计算位置 */
if (_ignoreAnchorPointForPosition)
{
x += _anchorPointInPoints.x;
y += _anchorPointInPoints.y;
}

/* 旋转 */
float cx = 1, sx = 0, cy = 1, sy = 0;
if (_rotationZ_X || _rotationZ_Y)
{
float radiansX = -CC_DEGREES_TO_RADIANS(_rotationZ_X);
float radiansY = -CC_DEGREES_TO_RADIANS(_rotationZ_Y);
cx = cosf(radiansX);
sx = sinf(radiansX);
cy = cosf(radiansY);
sy = sinf(radiansY);
}

bool needsSkewMatrix = ( _skewX || _skewY );

/* 计算最终的坐标 */
if (! needsSkewMatrix && !_anchorPointInPoints.equals(Point::ZERO))
{
x += cy * -_anchorPointInPoints.x * _scaleX + -sx * -_anchorPointInPoints.y * _scaleY;
y += sy * -_anchorPointInPoints.x * _scaleX + cx * -_anchorPointInPoints.y * _scaleY;
}

/* 变换矩阵 */
kmScalar mat[] = {
cy * _scaleX, sy * _scaleX, 0, 0,
-sx * _scaleY, cx * _scaleY, 0, 0,
0, 0, _scaleZ, 0,
x, y, z, 1 };

kmMat4Fill(&_transform, mat);

/* 旋转矩阵 */
if(_rotationY) {
kmMat4 rotY;
kmMat4RotationY(&rotY,CC_DEGREES_TO_RADIANS(_rotationY));
kmMat4Multiply(&_transform, &_transform, &rotY);
}
if(_rotationX) {
kmMat4 rotX;
kmMat4RotationX(&rotX,CC_DEGREES_TO_RADIANS(_rotationX));
kmMat4Multiply(&_transform, &_transform, &rotX);
}

/* 扭曲 */
if (needsSkewMatrix)
{
kmMat4 skewMatrix = { 1, (float)tanf(CC_DEGREES_TO_RADIANS(_skewY)), 0, 0,
(float)tanf(CC_DEGREES_TO_RADIANS(_skewX)), 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1};

kmMat4Multiply(&_transform, &_transform, &skewMatrix);

if (!_anchorPointInPoints.equals(Point::ZERO))
{
_transform.mat[12] += _transform.mat[0] * -_anchorPointInPoints.x + _transform.mat[4] * -_anchorPointInPoints.y;
_transform.mat[13] += _transform.mat[1] * -_anchorPointInPoints.x + _transform.mat[5] * -_anchorPointInPoints.y;
}
}

/* 自定义变换矩阵 */
if (_useAdditionalTransform)
{
kmMat4Multiply(&_transform, &_transform, &_additionalTransform);
}

_transformDirty = false;
}

return _transform;
}

可以看到,Node的位置,大小,旋转等成员变量都是在这里使用。

PS:位置变换&大小变换 == 位置变换矩阵x大小变换矩阵

cocos2dx里面有一系列转换的函数,如获取当前节点在世界坐标的位置
这里就列出convertToWorldSpace的相关方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
kmMat4 Node::getNodeToWorldTransform() const
{
kmMat4 t = this->getNodeToParentTransform();

for (Node *p = _parent; p != nullptr; p = p->getParent())
kmMat4Multiply(&t, &p->getNodeToParentTransform(), &t);

return t;
}

kmMat4 Node::getWorldToNodeTransform() const
{
kmMat4 tmp, tmp2;

tmp2 = this->getNodeToWorldTransform();
kmMat4Inverse(&tmp, &tmp2);
return tmp;
}


Point Node::convertToWorldSpace(const Point& nodePoint) const
{
kmMat4 tmp = getNodeToWorldTransform();
kmVec3 vec3 = {nodePoint.x, nodePoint.y, 0};
kmVec3 ret;
kmVec3Transform(&ret, &vec3, &tmp);
return Point(ret.x, ret.y);

}

从上面几个方法可以知道

  1. 求当前节点到根节点的所有变换矩阵乘积 — 矩阵T
  2. 求T其逆矩阵 — 矩阵iT
  3. 根据逆矩阵和当前相对于父节点的位置,便可以得到世界坐标的位置。

PS:涉及到线性代数逆矩阵相关东西 —- 3D数学第九章


渲染相关

visit

1
void Node::visit(Renderer* renderer, const kmMat4 &parentTransform, bool parentTransformUpdated)
{
    if (!_visible) { return; }

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

    /* 矩阵栈 */
    /* 要被弃用 */
    kmGLPushMatrix();
    kmGLLoadMatrix(&_modelViewTransform);

    int i = 0;

    /* 渲染树 */
    /* 递归渲染, dfs */
    if(!_children.empty())
    {
        /* 先对子儿子们排序,就是std::vector的排序 */
        sortAllChildren();

        /* 先绘制在该Node下面的 */
        for( ; i < _children.size(); i++ )
        {
            auto node = _children.at(i);

            if ( node && node->_localZOrder < 0 )
                node->visit(renderer, _modelViewTransform, dirty);
            else
                break;
        }
        /* 然后绘制该节点 */
        this->draw(renderer, _modelViewTransform, dirty);

        /* 然后绘制在该Node上面的节点 */
        for(auto it=_children.cbegin()+i; it != _children.cend(); ++it)
            (*it)->visit(renderer, _modelViewTransform, dirty);
    }
    else
    {
        this->draw(renderer, _modelViewTransform, dirty);
    }

    /* 避免再次排序? */
    _orderOfArrival = 0;

    kmGLPopMatrix();
}

/* sortAllChildren的比较函数 */
ibool nodeComparisonLess(Node* n1, Node* n2)
{
    return( n1->getLocalZOrder() < n2->getLocalZOrder() ||
            ( n1->getLocalZOrder() == n2->getLocalZOrder() 
           && n1->getOrderOfArrival() < n2->getOrderOfArrival() )
    );
}

这里的在visit中_orderOfArrival在渲染结束之后被置为0,这里结合nodeComparisonLess这个比较函数来看,
_orderOfArrival是Node维护的,每个节点的值都不同,而_localZOrder是用户自定义的,当_localZOrder相同的时候才去比较_orderOfArrival
将_orderOfArrival置为0的意思,是表示该节点已经排序过了。再排序的时候就不用做多余的交换操作。

值得注意的是源码中的一句注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 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;

void Node::setLocalZOrder(int z)
{
if (_localZOrder == z)
return;

_localZOrder = z;
if (_parent)
{
_parent->reorderChild(this, z);
}

_eventDispatcher->setDirtyForNode(this);
}

void Node::reorderChild(Node *child, int zOrder)
{
CCASSERT( child != nullptr, "Child must be non-nil");
_reorderChildDirty = true;
child->setOrderOfArrival(s_globalOrderOfArrival++);
child->_setLocalZOrder(zOrder);
}

这里的问题应该是int溢出,根据上面的条件,下面这个等式

2147482647 / 60 / 3600 / 24 / 27(Nodes) = 15 (days)

不过这个问题出现的几率比较小,毕竟让这个s_globalOrderOfArrival整形溢出还是需要挺长时间的,毕竟不是每一帧都去改变很多的Node的_localZOrder值。

引擎相关

addChild & removeChild

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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");

/* std::vector的reserve */
if (_children.empty()) { this->childrenAlloc(); }

/* 这里把child加入cocos2d::Vector数组中 */
this->insertChild(child, zOrder);

#if CC_USE_PHYSICS
/* 略去 */
#endif

child->_tag = tag;

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

if( _running )
{
child->onEnter();
/* 当在onEnter里面addChild的时候,防止onEnterTransitionDidFinish调用两次 */
if (_isTransitionFinished) { child->onEnterTransitionDidFinish(); }
}

if (_cascadeColorEnabled) { updateCascadeColor(); }

if (_cascadeOpacityEnabled) { updateCascadeOpacity(); }
}

void Node::removeChild(Node* child, bool cleanup /* = true */)
{
if (_children.empty()) { return; }

ssize_t index = _children.getIndex(child);
if( index != CC_INVALID_INDEX )
/* 从cocos2d::Vector中删除 */
this->detachChild( child, index, cleanup );
}

onEnter & onExit

代码中略去的脚本相关的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void Node::onEnter()
{
_isTransitionFinished = false;

for( const auto &child: _children)
child->onEnter();

this->resume();

_running = true;
}

void Node::onEnterTransitionDidFinish()
{
_isTransitionFinished = true;
for( const auto &child: _children)
child->onEnterTransitionDidFinish();
}

void Node::onExitTransitionDidStart()
{
for( const auto &child: _children)
child->onExitTransitionDidStart();
}

void Node::onExit()
{
this->pause();

_running = false;

for( const auto &child: _children)
child->onExit();
}

onEnteronExit一样,都是通过递归调用去执行以当前节点为根的一颗渲染树=。=

Ending

这文章写了我好久,讲述下艰辛的历程:

Node –> ComponentContainer –> Entity Component System –> template –> c++泛型编程
|
|—–> kazmath –> 线性代数

大概是这样,主要是自己本身基础不够扎实导致的各种问题。
还是得仔细研究研究c++ primer 5th才是啊,不然连c++都不会了要。