CCNode是Cocos2d-x的一个非常重要的概念,所有可以被渲染或者包含可渲染的都是一个CCNode。最主要的CCNode有:CCScene,CCLayer,CCSprite,CCMenu。关于CCNode的源码分析网上大把,但是因为介绍的东西太多而导致里面的很多细节都不能涉及到。这篇文章主要是讨论一下节点的添加、移除和渲染,也就是我们经常用到的addChild、removeChild和draw。
1、addChild:
addChild有三个重载函数:参数分别是child(添加的节点)、order(渲染顺序)、tag(标签)
/*
* “add”逻辑只能在这个方法执行
* 如果一个类不拓展'addChild'的行为则只需重写这个方法
*/
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");
// 1、孩子数组为空则先申请空间
if( ! m_pChildren )
{
this->childrenAlloc();
}
// 2、插入子节点
this->insertChild(child, zOrder);
child->m_nTag = tag;
// 3、设置父节点和到达的顺序
child->setParent(this);
child->setOrderOfArrival(s_globalOrderOfArrival++);
// 4、如果添加的父节点正在运行,则调用子节点的下面两个方法
if( m_bRunning )
{
child->onEnter();
child->onEnterTransitionDidFinish();
}
}
void CCNode::addChild(CCNode *child, int zOrder)
{
CCAssert( child != NULL, "Argument must be non-nil");
this->addChild(child, zOrder, child->m_nTag);
}
void CCNode::addChild(CCNode *child)
{
CCAssert( child != NULL, "Argument must be non-nil");
this->addChild(child, child->m_nZOrder, child->m_nTag);
}
我们只看第一个函数就行,后面两个都是调用第一个函数的。
重点1:insertChild
// helper used by reorderChild & add
void CCNode::insertChild(CCNode* child, int z)
{
m_bReorderChildDirty = true; // 用于sortAllChildren排序
ccArrayAppendObjectWithResize(m_pChildren->data, child); // 将child添加到m_pChildren中
child->_setZOrder(z); // 设置渲染顺序
}
// 添加一个对象到指定的数组中。如果需要可以增加数组的容量
void ccArrayAppendArrayWithResize(ccArray *arr, ccArray *plusArr);
重点2:如果父节点处于运行状态,则会调用子节点的onEnter()和onEnterTransitionDidFinish()函数
2、removeChild
void CCNode::removeFromParent()
{
this->removeFromParentAndCleanup(true);
}
void CCNode::removeFromParentAndCleanup(bool cleanup)
{
if (m_pParent != NULL)
{
m_pParent->removeChild(this,cleanup);
}
}
void CCNode::removeChild(CCNode* child)
{
this->removeChild(child, true);
}
/* "remove" logic MUST only be on this method
* If a class want's to extend the 'removeChild' behavior it only needs
* to override this method
*/
void CCNode::removeChild(CCNode* child, bool cleanup)
{
// explicit nil handling
if (m_pChildren == NULL)
{
return;
}
// 如果包含该子节点,调用detachChild
if ( m_pChildren->containsObject(child) )
{
this->detachChild(child,cleanup);
}
}
void CCNode::removeChildByTag(int tag)
{
this->removeChildByTag(tag, true);
}
void CCNode::removeChildByTag(int tag, bool cleanup)
{
CCAssert( tag != kCCNodeTagInvalid, "Invalid tag");
CCNode *child = this->getChildByTag(tag);
if (child == NULL)
{
CCLOG("cocos2d: removeChildByTag(tag = %d): child not found!", tag);
}
else
{
this->removeChild(child, cleanup);
}
}
void CCNode::removeAllChildren()
{
this->removeAllChildrenWithCleanup(true);
}
void CCNode::removeAllChildrenWithCleanup(bool cleanup)
{
// not using detachChild improves speed here
if ( m_pChildren && m_pChildren->count() > 0 )
{
CCObject* child;
CCARRAY_FOREACH(m_pChildren, child)
{
CCNode* pNode = (CCNode*) child;
if (pNode)
{
// IMPORTANT:
// -1st do onExit
// -2nd cleanup
if(m_bRunning)
{
pNode->onExitTransitionDidStart();
pNode->onExit();
}
if (cleanup)
{
pNode->cleanup();
}
// set parent nil at the end
pNode->setParent(NULL);
}
}
m_pChildren->removeAllObjects();
}
}
void CCNode::detachChild(CCNode *child, bool doCleanup)
{
// IMPORTANT:
// -1st do onExit
// -2nd cleanup
// 第一步执行OnExit,第二步执行cleanup
if (m_bRunning)
{
child->onExitTransitionDidStart();
child->onExit();
}
// If you don't do cleanup, the child's actions will not get removed and the
// its scheduledSelectors_ dict will not get released!
// 如果不执行cleanup,子节点的动作不会被移除,定时器也不会被释放
if (doCleanup)
{
child->cleanup();
}
// set parent nil at the end
// 设置父节点为空
child->setParent(NULL);
// 从数组中移除
m_pChildren->removeObject(child);
}
不管是removeFromParent还是removeChildByTag还是removeAllChild,最终都是调用removeChild,removeChild则调用detachChild。
1、如果父节点正在运行,调用子节点的onExitTransitionDidStart()和onExit()。
2、关于cleanup问题,如果执行cleanup函数,则会移除该节点的所有动作、定时器和其子节点的cleanup。不执行的话子节点动作不会被移除,定时器也不会被释放。
3、设置父节点为空并从数组中移除。
关于cleanup:
void CCNode::cleanup()
{
// 停止所有动作和定时器
this->stopAllActions();
this->unscheduleAllSelectors();
if ( m_eScriptType != kScriptTypeNone)
{
CCScriptEngineManager::sharedManager()->getScriptEngine()->executeNodeEvent(this, kCCNodeOnCleanup);
}
// 调用所有子节点的cleanup函数
arrayMakeObjectsPerformSelector(m_pChildren, cleanup, CCNode*);
}
3、draw
// 重写这个方法来绘制你的自身节点
virtual void draw(void);
// 递归访问该节点的子节点并渲染
virtual void visit(void);
draw是给子节点重写的,因为CCNode只是作为一个基类,所以这个方法是空的,但是实体类比如CCSprite或者CCMenu都是重写该方法。
visit是递归访问该节点的子节点并渲染。
void CCNode::visit()
{
// quick return if not visible. children won't be drawn.
// 1.先行处理
if (!m_bVisible)
{
return;
}
kmGLPushMatrix(); // 矩阵压栈
// 处理Grid特效
if (m_pGrid && m_pGrid->isActive())
{
m_pGrid->beforeDraw();
}
// 2.应用转换,在这里进行坐标转换
this->transform();
// 3.递归画图
CCNode* pNode = NULL;
unsigned int i = 0;
if(m_pChildren && m_pChildren->count() > 0)
{
// 存在的子节点排序
sortAllChildren();
// draw children zOrder <0
// 绘制 zOrder <0 的子节点
ccArray *arrayData = m_pChildren->data;
for( ; i num; i++ )
{
pNode = (CCNode*) arrayData->arr[i];
if ( pNode && pNode->m_nZOrder <0 )
{
pNode->visit();
}
else
{
break;
}
}
// self draw
// 绘制自身
this->draw();
// 绘制剩余的子节点
for( ; i num; i++ )
{
pNode = (CCNode*) arrayData->arr[i];
if (pNode)
{
pNode->visit();
}
}
}
else
{
// 没有子节点:直接绘制自身
this->draw();
}
// reset for next frame
// 4.恢复工作
m_uOrderOfArrival = 0;
if (m_pGrid && m_pGrid->isActive())
{
m_pGrid->afterDraw(this);
}
kmGLPopMatrix(); // 矩阵出栈
}
关于节点的就到这里,希望对大家有帮助!