一、数据结构
// 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 insertChild
void 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);
// 创建SpriteFrame
Vector<SpriteFrame *> animFrames( 15 );
for(......) animFrames->pushBack(cache->getSpriteFrameByName(...));
sprite->runAction(Animate::create( Animation::createWithSpriteFrames( animFrames, 0.3f ) ));