一、数据结构
// SpriteBatchNode的子节点都将在一个GL draw call中完成。// SpriteBatchNode的本质可看成是TextureAtlas::drawQuad()// SpriteBatchNode的子节点都必须是Sprite或者Sprite的子类// SpriteBatchNode的子节的纹理参数都只能是一样的,Filtering(过滤)、Wrapping(封装),因为都是同一个Texture纹理。class CC_DLL SpriteBatchNode : public Node, public TextureProtocol { // 纹理集,包含以下关键数据: // 一张纹理 // n个矩形的顶点数据、顶点索引,一个矩形就对应一个Sprite // 因此,TextureAtlas可以在一个GL draw call中绘制多个矩形,也即多个Sprite,这就是SpriteBatchNode的基础。 TextureAtlas* _textureAtlas; BlendFunc _blendFunc; // 混合函数 BatchCommand _batchCommand; // SpriteBatchNode是通过batchCommand绘制 // batchCommand就是封装TextureAtlas::drawQuad // all 后代: children, grand children, etc... // 注意这里是std::vector保存,是weak reference。 std::vector<Sprite*> _descendants;}
二、创建
class CC_DLL SpriteBatchNode : public Node, public TextureProtocol{ static const int DEFAULT_CAPACITY = 29; // tex: 使用的纹理 // capacity: 预计子节点的最大数量,超出将触发内存重构,最好不要超过这个数量。 static SpriteBatchNode* createWithTexture(Texture2D* tex, ssize_t capacity = DEFAULT_CAPACITY); static SpriteBatchNode* create(const std::string& fileImage, ssize_t capacity = DEFAULT_CAPACITY); bool initWithTexture(Texture2D* tex, ssize_t capacity = DEFAULT_CAPACITY); bool initWithFile(const std::string& fileImage, ssize_t capacity = DEFAULT_CAPACITY);}
三、绘制
visit:遍历
// 重写了visit方法// 遍历逻辑和Node::visit几乎一样,不同的地方是没有调用子节点的draw// 而是在SpriteBatchNode::draw中完成子节点的全部绘制void SpriteBatchNode::visit(Renderer *renderer, const Mat4 &parentTransform, uint32_t parentFlags){ CC_PROFILER_START_CATEGORY(kProfilerCategoryBatchSprite, "CCSpriteBatchNode - visit"); if (! _visible) { return; } // 排序子节点,注意这是自己实现的方法,和Node::sortAllChildren不同,主要工作如下: // 工作一:生成UI树的绘制顺序,具体做法:执行Node::sortAllChildren逻辑,按localZOrder从小到大排序 // 工作二:将上面的绘制顺序映射到TextureAtlas中的_quads数组的索引上,也即索引更小的_quad对应的就是先绘制的Sprite sortAllChildren(); uint32_t flags = processParentFlags(parentTransform, parentFlags); if (isVisitableByVisitingCamera()) { // 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->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"); }}
draw:绘制
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( // batchCommand绘制,其实就是对TextureAtlas::drawQuads()的封装。 _globalZOrder, getGLProgram(), _blendFunc, _textureAtlas, // 绘制的纹理、顶点数据和索引都在这里。 transform, flags); renderer->addCommand(&_batchCommand);}
四、addChild
void SpriteBatchNode::addChild(Node *child, int zOrder, int tag){ CCASSERT(child != nullptr, "child should not be null"); // child必须是Sprite或者Sprite子类。 CCASSERT(dynamic_cast<Sprite*>(child) != nullptr, "CCSpriteBatchNode only supports Sprites as children"); Sprite *sprite = static_cast<Sprite*>(child); // child必须都是用相同的Texture CCASSERT(sprite->getTexture()->getName() == _textureAtlas->getTexture()->getName(), "CCSprite is not using the same texture id"); Node::addChild(child, zOrder, tag); // 添加到UI树,visit遍历时,按localZOrder生成绘制顺序 appendChild(sprite);}// addChild helper, faster than insertChildvoid SpriteBatchNode::appendChild(Sprite* sprite){ _reorderChildDirty = true; sprite->setBatchNode(this); sprite->setDirty(true); if(_textureAtlas->getTotalQuads() == _textureAtlas->getCapacity()) { increaseAtlasCapacity(); } // 加入到后代容器_descendants的默认。 _descendants.push_back(sprite); // atlasIndex和在_descendants的索引一样。 int index = static_cast<int>(_descendants.size() - 1); sprite->setAtlasIndex(index); // 将sprite的顶点数据插入到atlasIndex = index位置处。 // atlasIndex即表示_textureAtlas._quads中的第几个quad,_textureAtlas.drawQuads是按atlasIndex从小到大绘制的。 // atlasIndex决定了每个后代的绘制顺序。 V3F_C4B_T2F_Quad quad = sprite->getQuad(); _textureAtlas->insertQuad(&quad, index); // add children recursively auto& children = sprite->getChildren();#if CC_SPRITE_DEBUG_DRAW // 绘制出三角形 ......#endif}
五、使用示例
// ********************************************// ************* 例 子 一:单张纹理// ********************************************auto batchNum = 50;auto batchNode = SpriteBatchNode::create(filename, batchNum);for(int i = 0; i < batchNum; i++) batchNode->addChild(Sprite::createWithTexture(batchNode->getTexture()));// ********************************************// ************* 例 子 二:SpriteSheet// ********************************************auto plist = "animations/ghosts.plist";auto plist_png = "animations/ghosts.png";SpriteFrameCache::getInstance()->addSpriteFramesWithFile( plist , plist_png );auto batchNode = SpriteBatchNode::create( "animations/ghosts.png" );batchNode->addChild(Sprite::createWithSpriteFrameName( "father.gif" ));batchNode->addChild(Sprite::createWithSpriteFrameName( "father1.gif" ));batchNode->addChild(Sprite::createWithSpriteFrameName( "father2.gif" ));// ********************************************// ************* 例 子 三:Animation动画// ********************************************auto cache = SpriteFrameCache::getInstance();auto plist = "animations/grossini-aliases.plist";auto plist_png = "animations/grossini-aliases.png";cache->addSpriteFramesWithFile( plist, plist_png );// 动画第一帧,animation动画的精灵auto sprite = Sprite::createWithSpriteFrameName( "grossini_dance_01.png" );addChild(sprite);auto batchNode = SpriteBatchNode::create( plist_png );addChild(batchNode);// 创建SpriteFrameVector<SpriteFrame *> animFrames( 15 );for(......) animFrames->pushBack(cache->getSpriteFrameByName(...));sprite->runAction(Animate::create( Animation::createWithSpriteFrames( animFrames, 0.3f ) ));