上节讲了一些CCNode中值得注意的函数,这节来讲讲CCNode的绘制链。这节的内容在整个cocos2d-x中是很重要的,因为绘制链不仅仅是为了绘制而已。正如前面看到的,cocos2d-x的绘制源头在主循环中的drawscene,而其中真正承当绘制任务的是:
// draw the scene
if (m_pRunningScene)
{
m_pRunningScene->visit();
}
一个visit函数就把整个游戏中要绘制的任务全部绘制了。我们这个running scene也是CCNode的子类,我们看看CCNode中的visit函数。
void CCNode::visit()
{
// quick return if not visible. children won't be drawn.
if (!m_bVisible)
{
return;
}
kmGLPushMatrix();
if (m_pGrid && m_pGrid->isActive())
{
m_pGrid->beforeDraw();
}
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();
}
// reset for next frame
m_uOrderOfArrival = 0;
if (m_pGrid && m_pGrid->isActive())
{
m_pGrid->afterDraw(this);
}
kmGLPopMatrix();
}
前面那个简单粗暴的判断虽然提高了效率,却也是导致getIsVisible函数不靠谱的根源。或许这个变量名需要改一改才行。这个函数有点多,一些绘制方面的细节,我就不介绍了。等到CCSprite的时候一起讲解,现在主要看它的绘制结构。先绘制ZOrder小于0的子节点,再绘制自己,最后绘制ZOrder大于0的子节点。注意到对于子节点,它是调用的visit函数,也就是说这个函数是一个递归调用。根节点是running scene,然后保证所有的child的draw函数都会按照这个顺序调用到。那个draw这个函数就是真正绘制自己的函数了。
因为CCNode是一个基类,本身不显示任何内容。所以就是一个简单的空函数了。
void CCNode::draw()
{
//CCAssert(0);
// override me
// Only use- this function to draw your stuff.
// DON'T draw your stuff outside this method
}
我们在CCNode的子节点中,最好不要重载visit函数,而把需要的逻辑实现在draw函数中去(可能有些和帧相关的逻辑)。当然,这也不是绝对的。
我们再看看CCNode是如何构建Children的。添加Child有三个函数,只有一个函数是主要的:
void CCNode::addChild(CCNode *child, int zOrder, int tag)
{
CCAssert( child != NULL, "Argument must be non-nil");
CCAssert( child->m_pParent == NULL,
"child already added. It can't be added again");
if( ! m_pChildren )
{
this->childrenAlloc();
}
this->insertChild(child, zOrder);
child->m_nTag = tag;
child->setParent(this);
child->setOrderOfArrival(s_globalOrderOfArrival++);
if( m_bRunning )
{
child->onEnter();
child->onEnterTransitionDidFinish();
}
}
为了效率,这里有个延时分配处理。insertChild便是将Child添加到m_pChildren中。这个函数封装的是C类型的API,而且层次非常深,先不讲。之后的代码便是处理一些与子节点的小的细节。最后那个m_bRunning很值得注意。我们可以看到一个子节点在添加到父节点中时,onEnter一般会被调用到,其代码如下:
void CCNode::onEnter()
{
arrayMakeObjectsPerformSelector(m_pChildren, onEnter, CCNode*);
this->resumeSchedulerAndActions();
m_bRunning = true;
if (m_eScriptType != kScriptTypeNone)
{
CCScriptEngineManager::sharedManager()->getScriptEngine()->
executeNodeEvent(this, kCCNodeOnEnter);
}
}
第一个函数是一个宏,是把所有Children的onEnter都要调用一遍。然后将自己标记为活动的。如果自己onEnter了,那么子节点也要onEnter,注意这里就没有什么逻辑判断了。之后的处理是为了支持脚本。接下来onEnterTransitionDidFinish便会调用到了。
void CCNode::onEnterTransitionDidFinish()
{
arrayMakeObjectsPerformSelector(m_pChildren,
onEnterTransitionDidFinish, CCNode*);
if (m_eScriptType == kScriptTypeJavascript)
{
CCScriptEngineManager::sharedManager()->getScriptEngine()->
executeNodeEvent(this, kCCNodeOnEnterTransitionDidFinish);
}
}
举个例子,一般会有如下的代码:
CCSprite* sprite = CCSprite::create("test");
CCSprite* child_1 = CCSprite::create("child_1");
child_1->runAction(CCMoveTo::create(1.0f, CCPointZero));
CCSprite* child_2 = CCSprite::create("child_2");
sprite->addChild(child_1, 1, 1);
sprite->addChild(child_2, 2, 2);
addChild(sprite);
在sprite addChild的时候 onEnter这些函数是不会调用到的,child_1的action也不会运行。一切的开始,都是sprite被添加到一个活动的节点上了。那么整个枝节也就活动起来了。一般来将,如果你要重载这两个函数,onEnter处理内部逻辑,onEnterTransitionDidFinish处理外部逻辑较佳。而且都要进行super call。对应还有两个函数:
void CCNode::onExitTransitionDidStart()
{
arrayMakeObjectsPerformSelector(m_pChildren,
onExitTransitionDidStart, CCNode*);
if (m_eScriptType == kScriptTypeJavascript)
{
CCScriptEngineManager::sharedManager()->getScriptEngine()->
executeNodeEvent(this, kCCNodeOnExitTransitionDidStart);
}
}
void CCNode::onExit()
{
this->pauseSchedulerAndActions();
m_bRunning = false;
if ( m_eScriptType != kScriptTypeNone)
{
CCScriptEngineManager::sharedManager()->getScriptEngine()->
executeNodeEvent(this, kCCNodeOnExit);
}
arrayMakeObjectsPerformSelector(m_pChildren, onExit, CCNode*);
}
前面讲过,m_pIsRunning相当如时间静止,如果你需要暂停一个界面。可以简单的调用这个界面的onExit()函数。而且onExit和onExitTransitionDidStart要成对调用。如果想要恢复,那么再成对调用onEnter和onEnterTransitionDidFinish。我的建议是,如果团队都清楚这个实现,那么在重载函数的时候就会谨慎小心。一些出格的使用也不会有什么问题。否则,这么重要的函数最好不要去重载,尤其是onExitTransitionDidStart和onEnterTransitionDidFinish。与引擎保持一段距离是很重要的。