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中添加节点之后崩溃

 

 

时间: 2024-12-31 23:26:47

Cocos2d-x不要随便在onEnter里面addChild的相关文章

实例介绍Cocos2d-x物理引擎:碰撞检测

碰撞检测是使用物理引擎的一个重要目的使用物理引擎可以进行精确的碰撞检测而且执行的效率也很高.在Cocos2d-x 3.x中使用事件派发机制管理碰撞事件EventListenerPhysicsContact是碰撞事件监听器.碰撞检测相关的API我们在前面一节介绍过了下面通过一个实例介绍碰撞检测的实现.这个实例的运行后的场景如图所示当场景启动后玩家可以触摸点击屏幕每次触摸时候就会在触摸点生成一个新的精灵精灵的运行是自由落体运动.当这些精灵之间发生接触时候它们的颜色被设置为黄色分离后颜色又恢复到原来状

Fast TileMap

概述 在游戏中常常会有丰富的背景元素,如果直接使用大的背景图实现,这会造成资源浪费.TileMap就是为了解决这问题而产生的.Cocos2d-x支持使用Tile地图编辑器创建的TMX格式的地图. Cocos2d-x为我们提供了TMXTileMap和TMXLayer两个类来处理瓦片地图.通过使用TMXTileMap和TMXLayer,我们可以很方便的加载TMX格式的地图文件,获取地图上的图层.对象.属性等信息. 新发布的3.2版本,对瓦片地图进行了大幅改进,通过自动裁剪不在视图范围内的瓦片来提升性

cocos2dx addchild-cocos2dx中关于addChild()引用计数具体位置

问题描述 cocos2dx中关于addChild()引用计数具体位置 都说cocos2d中使用addChild()时引用计数加一,请问具体在哪儿?`void Node::addChild(Node *child, int zOrder, int tag) { CCASSERT( child != nullptr, "Argument must be non-nil"); CCASSERT( child->_parent == nullptr, "child alread

《Cocos2D权威指南》——1.4 深入学习HelloCocos2D项目

1.4 深入学习HelloCocos2D项目 在完成了第一个HelloCocos2D项目后,如果读者不仅想看到飞机在屏幕上飞行,还想知道这一切是怎样实现的,我们不妨来一起探究其中的每一行代码.1.4.1 初识场景和节点 要想理解HelloCocos2D这个项目,首先要了解场景(CCScene).层(CCLayer)和节点(CCNode)的概念. Cocos2D游戏是由不同的场景构成的,由导演(CCDirector)负责运行和切换各个场景.在Cocos2D中,CCDirector在任何一个时间点上

[cocos2d-x]cocos2d和cocos2d-x的一些通用性

不得不说要说总结,因为Cocos2d-X的代码和Cocos2d-iphone两个引擎除了语言不同外(Cocos2d-X使用C++,Cocos2d-iphone使用Object-C)可以说没有其他差异.         下面举例对比几段代码来说明吧:        创建添加一个精灵代码对比:               使用Cocos2d-X:  view plain //---------Cocos2d-X代码部分--      //创建一个精灵      CCSprite *spriteTem

《Cocos2D权威指南》——3.2 CCNode节点类

3.2 CCNode节点类 CCNode是Cocos2D中最重要的类,同时也是所有节点的基类.它是一个抽象类,没有视觉表现,定义了所有节点都通用的属性和方法.在Cocos2D中,所有要绘制到屏幕的对象,或是自身包含要绘制到屏幕中的对象,都属于CCNode类.最重要的几个CCNode类分别是CCScene.CCLayer.CCSprite.CCMenu,这也是我们本章要重点学习的内容.CCNode的主要作用:包含其他的CCNode节点(addChild.getChildByTag.removeCh

Cocosd2d实例教程(六) Cocos2d实现屏幕背景的自动滚动

1.介绍 实现屏幕背景的自动滚动是游戏常遇到的功能,这样我们就不用绘制很长的背景图片,只要设计一张就可以,省时省力.这章将实现这个功能,并把源代码贡献给大家,废话不多说,先上个图,其实是动态的,只是截动态图有点麻烦. 2.代码实现部分 屏幕的动态滚动主要是一个刷新机制的问题. 第一步,还是进入HelloWorldLayer.h中定义一些节点的对象 <span style="color:#362e2b">#import <GameKit/GameKit.h> //

Cocosd2d实例教程(三) Cocos2d瓦片地图的导入

上一节讲了如何使用Tiled制作瓦片地图,下面讲一下如何将地图导入游戏中. 第一步:将生成的文件导入resource中,如图,分别为地图和图片集 第二步:在HelloWorldLayer.h中修改代码,有一定基础的人还是比较好理解的. #import <GameKit/GameKit.h> // When you import this file, you import all the cocos2d classes #import "cocos2d.h" // Hello

IOS开发:Cocos2d触摸分发原理分析

  触摸是iOS程序的精髓所在,良好的触摸体验能让iOS程序得到非常好的效果,例如Clear.鉴于同学们只会用cocos2d的 CCTouchDispatcher 的 api 但并不知道工作原理,但了解触摸分发的过程是极为重要的.毕竟涉及到权限.两套协议等的各种分发. 本文以cocos2d-iphone源代码为讲解.cocos2d-x 于此类似,就不过多赘述了. 零.cocoaTouch的触摸 在讲解cocos2d触摸协议之前,我觉得我有必要提一下CocoaTouch那四个方法.毕竟cocos2