Cocos2d-x不要隨便在onEnter里面addChild


使用任何版本的Cocos2d-x(1.x,2.x,3.0),在onEnter中調用addChild,都要小心謹慎,因為它有可能導致兩種莫名其妙的BUG,莫名其妙的BUG當然難以定位了!更何況這個BUG隱藏在引擎的底層。

接下來是場景還原:

在某個節點下,需要執行這樣一段邏輯,在游戲場景中,添加幾個節點,由於游戲場景就是該節點的父節點,於是就直接getParent然后調用父節點的addChild,在onEnter函數中添加看上去比較合適,因為這時候該節點的父節點可以訪問,而在init函數中,還沒有被添加到游戲場景中

神奇的事情發生了,在這之后添加的節點,都無法播放動畫了,而把節點添加的位置,移到該節點之前進行添加,動畫就可以正常播放,檢查了一下代碼,無果,先記下該問題

接下來又有一件神奇的事情發生了,我們的程序崩潰了!用排除法發現,是在onEnter下添加節點導致的崩潰,但是有趣的是,onEnter下的一個for循環添加5個節點,當我把節點數量該為4的時候,程序又可以正常執行了!而添加到5或者更多的時候,程序又崩潰了!

看到這里我仿佛明白了什么,打開2dx的CCNode::addChild的代碼,在每次addChild的時候,會根據當前數組的容量,進行擴容

void ccArrayDoubleCapacity(ccArray *arr)
{
    arr->max *= 2;
    CCObject** newArr = (CCObject**)realloc( arr->arr, arr->max * sizeof(CCObject*) );
    // will fail when there's not enough memory
    CCAssert(newArr != 0, "ccArrayDoubleCapacity failed. Not enough memory");
    arr->arr = newArr;
}

上面的代碼用realloc重新分配了內存,但是,在CCNode的onEnter中,是在遍歷這個數組,執行所有子節點的onEnter

void CCNode::onEnter()
{
    arrayMakeObjectsPerformSelector(m_pChildren, onEnter, CCNode*);
    
    this->resumeSchedulerAndActions();

    m_bIsRunning = true;

    if (m_eScriptType != kScriptTypeNone)
    {
        CCScriptEngineManager::sharedManager()->getScriptEngine()->executeNodeEvent(this, kCCNodeOnEnter);
    }
}

在arrayMakeObjectsPerformSelector中,調用到了2dx底層的一個宏,CCARRAY_FOREACH

#define CCARRAY_FOREACH(__array__, __object__)                                                                \
    if ((__array__) && (__array__)->data->num > 0)                                                            \
    for(CCObject** arr = (__array__)->data->arr, **end = (__array__)->data->arr + (__array__)->data->num-1;    \
    arr <= end && (((__object__) = *arr) != NULL/* || true*/);                                                \
    arr++)

這個宏用於遍歷CCArray,它是用指針偏移的方式進行遍歷,所以,當我們的數組擴容之后,指針的地址就變了,CCARRAY_FOREACH還在對原先的指針進行訪問,當然崩潰了
其實這個BUG很好解決,只需要修改一下CCARRAY_FOREACH的遍歷方式,改為下標訪問即可,在CCNode::onEnter函數下,將代碼調整為如下所示,BUG解決

void CCNode::onEnter()
{
    //arrayMakeObjectsPerformSelector(m_pChildren, onEnter, CCNode*);
    if (NULL != m_pChildren)
    {
        for (int i = 0; i < m_pChildren->count(); ++i)
        {
            ((CCNode*)(m_pChildren->data->arr[i]))->onEnter();
        }
    }
    
    this->resumeSchedulerAndActions();

    m_bIsRunning = true;

    if (m_eScriptType != kScriptTypeNone)
    {
        CCScriptEngineManager::sharedManager()->getScriptEngine()->executeNodeEvent(this, kCCNodeOnEnter);
    }
}

也許我不應該在onEnter里面addChild,但cocos2d-x更不應該讓我在onEnter中添加節點之后崩潰

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM