cocos2d中采用树形结构来管理场景中的所有对象。所有的对象都是CCNode的子类。那么我们先探探CCNode类的虚实,先上代码:
class CC_DLL CCNode : public CCObject
{
public:
virtual void update(float delta);
virtual void draw(void);
virtual void visit(void);
protected:
float m_fRotationX; ///< rotation angle on x-axis
float m_fRotationY; ///< rotation angle on y-axis
float m_fScaleX; ///< scaling factor on x-axis
float m_fScaleY; ///< scaling factor on y-axis
float m_fVertexZ; ///< OpenGL real Z vertex
CCPoint m_obPosition; ///< position of the node
float m_fSkewX; ///< skew angle on x-axis
float m_fSkewY; ///< skew angle on y-axis
CCCamera *m_pCamera; ///< a camera
int m_nZOrder; ///< z-order value that affects the draw order
CCArray *m_pChildren; ///< array of children nodes
CCNode *m_pParent; ///< weak reference to parent node
CCActionManager *m_pActionManager; ///< a pointer to ActionManager singleton, which is used to handle all the actions
}
从上面的结构可以看到用m_pChildren保存所有的子结点,用m_pParent记住了当前结点的父节点。而m_Zorder是表示所有子结点之间的渲染优先级。
CCDirector::drawScene再剖析
上一篇文章中每一帧CCDirector::drawScene中会调用:
m_pScheduler->update(m_fDeltaTime);
这个最终会调用到上面的update函数。可能有的读者会觉得奇怪,CCNode需要update么?其实有些结点需要,比如粒子系统就需要每帧更新所有粒子的参数。
另外在上一篇文章中CCDirector::drawScene中还会调用:
m_pRunningScene->visit();
这个m_pRunningScene是一个CCScene对象,也是CCNode的子类,相当于遍历场景结点树并进行绘制,让我们看看CCNode::visit都做了些什么?
void CCNode::visit()
{
// quick return if not visible. children won't be drawn.
if (!m_bVisible)
{
return;
}
kmGLPushMatrix();
this->transform();
CCNode* pNode = NULL;
unsigned int i = 0;
if(m_pChildren && m_pChildren->count() > 0)
{
sortAllChildren();
// draw children zOrder < 0
ccArray *arrayData = m_pChildren->data;
for( ; i < arrayData->num; i++ )
{
pNode = (CCNode*) arrayData->arr[i];
if ( pNode && pNode->m_nZOrder < 0 )
{
pNode->visit();
}
else
{
break;
}
}
// self draw
this->draw();
for( ; i < arrayData->num; i++ )
{
pNode = (CCNode*) arrayData->arr[i];
if (pNode)
{
pNode->visit();
}
}
}
else
{
this->draw();
}
kmGLPopMatrix();
}
从这段代码可以看到,CCNode的所有子结点绘制的顺序是按照m_nZOrder 来的,先绘制m_nZOrder < 0的,然后绘制自身,最后绘制m_nZOrder >=0的。对于每个子类需要重构this->draw()来实现自己的绘制方法。在这个函数中,还有一个非常重要的方法:this->transform(),不同坐标系的变换就靠它了!
void CCNode::transform()
{
kmMat4 transfrom4x4;
CCAffineTransform tmpAffine = this->nodeToParentTransform();
CGAffineToGL(&tmpAffine, transfrom4x4.mat);
transfrom4x4.mat[14] = m_fVertexZ;
kmGLMultMatrix( &transfrom4x4 );
// XXX: Expensive calls. Camera should be integrated into the cached affine matrix
if ( m_pCamera != NULL && !(m_pGrid != NULL && m_pGrid->isActive()) )
{
m_pCamera->locate();
}
}
因为树形结构是采用相对的坐标表示的,所有我们只需要计算相对于父结点的变换矩阵transfrom4x4 ,然后乘以当前的栈顶的变换矩阵,特别注意的是:在访问的过程中变换矩阵式以堆栈的形式保存的
kmGLPushMatrix();
......
kmGLPopMatrix();
访问前后分别将变换矩阵状态压栈和出栈。另外如果CCNode有自己的相机的话,则还需要将相机的变换矩阵(也就是我们常说的View Matrix)计算进去。)为什么CCNode可以有自己的相机呢?比如说我想要每个对象缩放,那我可以让这个CCNode的相机拉远,当然我们也可以设置缩放参数。显然后者更简单,不过当碰到前一种前一种情况更适合的时候不要忘记了他的存在。
变换矩阵的更新过程中都是使用脏更新。