Cocos2d-x不要随便在onEnter里面addChild
2013-12-23 16:12
369 查看
使用任何版本的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的时候,会根据当前数组的容量,进行扩容
上面的代码用realloc重新分配了内存,但是,在CCNode的onEnter中,是在遍历这个数组,执行所有子节点的onEnter
在arrayMakeObjectsPerformSelector中,调用到了2dx底层的一个宏,CCARRAY_FOREACH
这个宏用于遍历CCArray,它是用指针偏移的方式进行遍历,所以,当我们的数组扩容之后,指针的地址就变了,CCARRAY_FOREACH还在对原先的指针进行访问,当然崩溃了
其实这个BUG很好解决,只需要修改一下CCARRAY_FOREACH的遍历方式,改为下标访问即可,在CCNode::onEnter函数下,将代码调整为如下所示,BUG解决
也许我不应该在onEnter里面addChild,但cocos2d-x更不应该让我在onEnter中添加节点之后崩溃
接下来是场景还原:
在某个节点下,需要执行这样一段逻辑,在游戏场景中,添加几个节点,由于游戏场景就是该节点的父节点,于是就直接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中添加节点之后崩溃
相关文章推荐
- Cocos2d-x不要随便在onEnter里面addChild
- Cocos2d-x不要随便在onEnter里面addChild
- Cocos2d-x不要随便在onEnter里面addChild
- cocos2d 学习笔记 - 【转载】cocos2dx 不要直接在 onEnter 里面 addTargetedDelegate (或 addStandardDelegate)
- cocos2dx 不要直接在 onEnter 里面 addTargetedDelegate (或 addStandardDelegate)
- cocos2dx 不要直接在 onEnter 里面 addTargetedDelegate (或 addStandardDelegate)
- 不要随便牵手,也不要随便放手
- Cocos2d-x里面如何实现MVC(五)
- (译)在cocos2d里面如何制作按钮:简单按钮、单选按钮和开关按钮
- (译)在cocos2d里面如何制作按钮:简单按钮、单选按钮和开关按钮
- (译)在cocos2d里面如何使用物理引擎box2d:弹球
- cocos2d-x 避免手打输入项目cpp文件到android.mk里面编译
- Cocos2d-x init() 和 onEnter() 区别
- Cocos2d-x里面如何实现MVC(四)
- Cocos2d-x 的onEnter() 和 onEnterTransitionDidFinish() 场景延时跳转
- 请不要缩在项目或产品的“龟壳”里面
- 关于cocos2d-x中init()方法和OnEnter方法的区别
- 在 往CCLayer里面加 Notification通知的时候,一定要加在OnEnter和OnExit里面
- 6.在cocos2d里面如何使用物理引擎box2d:弹球
- cocos2d里面使用精灵表单(spritesheet)的好处