cocos2d-x 源码剖析(6)

上节讲了一些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。与引擎保持一段距离是很重要的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值