SpriteBatchNode继承Node,并实现了TextureProtocol接口,重写了Node的addChild()方法,visit()方法以及draw()方法。
addChild()方法限制其子元素只能是Sprite,
并且子元素与SpriteBatchNode必须使用同一个Texture2D对象。
visit()用于阻止元素向下遍历,将所有子元素的绘制工作交给自己处理。
draw()方法使用BatchNodeCommand将绘制命令发送到RenderQueue,从而对所有子元素进行批绘制。
SpriteBatchNode使用TextureAtlas存储所有子精灵的顶点信息。TextureAtlas包含一个V3F_C4B_T2F_Quad数组和一个Texture2D对象,提供对quads数组的添加,删除,修改,排序等功能。这样,SpriteBatchNode所做的主要事情就是将与子元素相关的顶点信息存储到TextureAtlas中。最后,TextureAtlas提供了绘制quads的方法,
BatchCommand就是通过drawQuads()方法绘制的
代码流程:
1 调用
auto spBatchNode = SpriteBatchNode::create("bbb.png"); spBatchNode->setPosition(Point::ZERO); scene->addChild(spBatchNode); spBatchNode->setPosition(200,200); // spBatchNode->setScale(0.1); auto sp = Sprite::createWithTexture(spBatchNode->getTexture()); sp->setPosition(0,0); spBatchNode->addChild(sp,1); sp = Sprite::createWithTexture(spBatchNode->getTexture()); sp->setPosition(10,10); spBatchNode->addChild(sp,-1);
2 看一下SpriteBatchNode内部方法
初始化方法
bool SpriteBatchNode::initWithTexture(Texture2D *tex,ssize_t capacity) { CCASSERT(capacity>=0,"Capacity must be >= 0"); _blendFunc = BlendFunc::ALPHA_PREMULTIPLIED; if(tex->hasPremultipliedAlpha())//alpha预乘,忽略 { _blendFunc = BlendFunc::ALPHA_NON_PREMULTIPLIED; } //使用textureAtlas存储所有子精灵的顶点信息,通过对quads数组的添加,删除,修改排序等 _textureAtlas = new TextureAtlas(); if (capacity == 0) { capacity = DEFAULT_CAPACITY; } _textureAtlas->initWithTexture(tex,capacity); updateBlendFunc(); _children.reserve(capacity); //分配空间大小 _descendants.reserve(capacity); //定义属于自己的着色器 setGLProgramState(GLProgramState::getOrCreateWithGLProgramName(GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR)); return true; }
void SpriteBatchNode::addChild(Node * child,int zOrder,const std::string &name) { CCASSERT(child != nullptr,"child should not be null"); CCASSERT(dynamic_cast<Sprite*>(child) != nullptr,"CCSpriteBatchNode only supports Sprites as children"); Sprite *sprite = static_cast<Sprite*>(child); // check Sprite is using the same texture id CCASSERT(sprite->getTexture()->getName() == _textureAtlas->getTexture()->getName(),"CCSprite is not using the same texture id"); Node::addChild(child,zOrder,name); appendChild(sprite); } // addChild helper,faster than insertChild void SpriteBatchNode::appendChild(Sprite* sprite) { _reorderChildDirty=true; sprite->setBatchNode(this); sprite->setDirty(true); if(_textureAtlas->getTotalQuads() == _textureAtlas->getCapacity()) { increaseAtlasCapacity();//重新调整容器大小, } _descendants.push_back(sprite);//所有的子节点。 int index = static_cast<int>(_descendants.size()-1); sprite->setAtlasIndex(index);//设置sprite的渲染节点为TextureAtlas的第index个数据 V3F_C4B_T2F_Quad quad = sprite->getQuad(); //把新sprite的顶点信息放到textureAtlas,供绘制使用,此时的顶点坐标还是在父亲中的坐标,不是世界坐标 _textureAtlas->insertQuad(&quad,index);//将sprite的顶点数据传入到TextureAtlas的index位置。 // add children recursively auto& children = sprite->getChildren(); for(const auto &child: children) { //递归调用 appendChild(static_cast<Sprite*>(child)); //将sprite的所有数据加入到descendants。 } }
遍历方法visit
// override visit // don‘t call visit on it‘s children void SpriteBatchNode::visit(Renderer *renderer,const Mat4 &parentTransform,uint32_t parentFlags) { CC_PROFILER_START_CATEGORY(kProfilerCategoryBatchSprite,"CCSpriteBatchNode - visit"); // CAREFUL: // This visit is almost identical to CocosNode#visit // with the exception that it doesn‘t call visit on it‘s children // // The alternative is to have a void Sprite#visit,but // although this is less maintainable,is faster // if (! _visible) { return; } //排序,写的挺不错 sortAllChildren(); //得到本地坐标转换世界坐标的矩阵 uint32_t flags = processParentFlags(parentTransform,parentFlags); // IMPORTANT: // To ease the migration to v3.0,we still support the Mat4 stack,// but it is deprecated and your code should not rely on it Director* director = Director::getInstance(); director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW,_modelViewTransform); draw(renderer,_modelViewTransform,flags); director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); // FIX ME: Why need to set _orderOfArrival to 0?? // Please refer to https://github.com/cocos2d/cocos2d-x/pull/6920 // setOrderOfArrival(0); CC_PROFILER_STOP_CATEGORY(kProfilerCategoryBatchSprite,"CCSpriteBatchNode - visit"); }
//override sortAllChildren //排序功能 void SpriteBatchNode::sortAllChildren() { if (_reorderChildDirty) { std::sort(std::begin(_children),std::end(_children),nodeComparisonLess); //sorted now check all children if (!_children.empty()) { //first sort all children recursively based on zOrder for(const auto &child: _children) { child->sortAllChildren(); } ssize_t index=0; //fast dispatch,give every child a new atlasIndex based on their relative zOrder (keep parent -> child relations intact) // and at the same time reorder descendants and the quads to the right index /* //快速发送,根据每个孩子的相对zOrder给每个孩子一个新的atlasIndex(保持父母 - >孩子的关系不变) //同时重新排序descendants数组和四边形到正确的索引 */ //此时的childern为根据localZ从小到大排序之后的数组 for(const auto &child: _children) { Sprite* sp = static_cast<Sprite*>(child); updateAtlasIndex(sp,&index); } } _reorderChildDirty=false; } } //更新sprite和sprite子节点的atlas数据的index。 void SpriteBatchNode::updateAtlasIndex(Sprite* sprite,ssize_t* curIndex) { auto& array = sprite->getChildren(); auto count = array.size(); ssize_t oldIndex = 0; /* 在sortAllChildren,根据localZ对children进行了排序,从小到大排序,因为之前的AtlasIndex为顺序赋值的,所以要重新设置AtlasIndex */ //如果没有子节点,那么sprite就是curIndex位置了 if( count == 0 ) { oldIndex = sprite->getAtlasIndex(); sprite->setAtlasIndex(*curIndex);//重新递增赋值 sprite->setOrderOfArrival(0); if (oldIndex != *curIndex){ //更新在_textureAtlas等中的位置 swap(oldIndex,*curIndex); } (*curIndex)++; } else { bool needNewIndex=true;//是否需要赋值新的索引 //先localZ<0的,然后sprite,最后localZ>0 //因为先对所有的子节点排序过。先localZ<0,然后localZ>0 //如果第一个localZ>=0那么先设置sprite的index if (array.at(0)->getLocalZOrder() >= 0) { //sprite的第一个孩子local都大于0,剩下的孩子都大于0,所以孩子绘制都在自己上面 //all children are in front of the parent oldIndex = sprite->getAtlasIndex(); sprite->setAtlasIndex(*curIndex); sprite->setOrderOfArrival(0); if (oldIndex != *curIndex) { swap(oldIndex,*curIndex); } (*curIndex)++; needNewIndex = false;//不需要新的索引,后面的按照顺序遍历就行 } for(const auto &child: array) { Sprite* sp = static_cast<Sprite*>(child); //找到了第一个localZorder>0的,设置sprite。 if (needNewIndex && sp->getLocalZOrder() >= 0) { oldIndex = sprite->getAtlasIndex(); sprite->setAtlasIndex(*curIndex); sprite->setOrderOfArrival(0); if (oldIndex != *curIndex) { this->swap(oldIndex,*curIndex); } (*curIndex)++; needNewIndex = false; } //递归调用 updateAtlasIndex(sp,curIndex); } //这种情况是:所有的子节点都是localZ<0。 //all children have a zOrder < 0) /* sprite的children已经遍历完成,最后轮到sprite自己 */ if (needNewIndex) { oldIndex = sprite->getAtlasIndex(); sprite->setAtlasIndex(*curIndex); sprite->setOrderOfArrival(0); if (oldIndex != *curIndex) { swap(oldIndex,*curIndex); } (*curIndex)++; } } }
swap把新排序的顶点数据更新到 quads中,用于为opengl提供基础数据
void SpriteBatchNode::swap(ssize_t oldIndex,ssize_t newIndex) { CCASSERT(oldIndex>=0 && oldIndex < (int)_descendants.size() && newIndex >=0 && newIndex < (int)_descendants.size(),"Invalid index"); V3F_C4B_T2F_Quad* quads = _textureAtlas->getQuads(); std::swap( quads[oldIndex],quads[newIndex] ); //update the index of other swapped item auto oldIt = std::next( _descendants.begin(),oldIndex ); auto newIt = std::next( _descendants.begin(),newIndex ); (*newIt)->setAtlasIndex(oldIndex); // (*oldIt)->setAtlasIndex(newIndex); std::swap( *oldIt,*newIt ); }
重新的元素draw方法,并不是真的绘制,而是把绘制命令放到了_batchCommand中
void SpriteBatchNode::draw(Renderer *renderer,const Mat4 &transform,uint32_t flags) { // Optimization: Fast Dispatch if( _textureAtlas->getTotalQuads() == 0 ) { return; } for(const auto &child: _children) { //更新孩子的坐标,递归 child->updateTransform(); } _batchCommand.init( _globalZOrder,getGLProgram(),_blendFunc,_textureAtlas,transform); renderer->addCommand(&_batchCommand); }