6.1 高效创建CSB
CocoStudio可以导出CSB或JSON格式的资源文件,在Cocos2d-x中使用CSLoader可以加载它们,正常情况下这两种格式所占的体积(打包之后),解析速度都是CSB格式会稍微好一些,但如果在CocoStudio中大量使用了嵌套CSB,那么这个CSB文件的加载会耗费很长的一段时间。
例如,在CocoStudio中制作一个背包界面,背包上的每一个格子使用的都是同一个背包格子CSB文件,CocoStudio中是允许复用CSB的。在CocoStudio项目中编辑时,场景、节点等文件是CSB格式(CocoStudio Design),在导出时可以导出为CSB格式(CocoStudio Binary)。假设拖曳了100个背包格子放到背包界面上,那么导出CSB时会导出一个背包界面的CSB,以及一个背包格子的CSB文件。如果导出的是JSON格式,那么这个JSON文件中会包含100个背包格子的详细信息,如节点结构、名字、位置、Tag等。CSB格式则只会保存一份背包格子的详细信息,在背包界面CSB文件中引用100次背包格子CSB文件。
这样来看,在嵌套的情况下,CSB格式的冗余程度要大大小于JSON格式,但如果测试一下,会发现加载这样的一个CSB要比加载JSON慢很多,可以说是效率极低。经过分析发现,这样一个CSB文件加载时的瓶颈主要不是在加载纹理上,而是在加载CSB文件上,CSLoader在加载这样一个商店界面CSB时,执行了101次的文件I/O操作,首先读取商店界面CSB文件进行解析,在解析过程中发现引用到了商店格子CSB文件,则对商店格子CSB文件进行读取,因为引用了100次,所以读取了100次。文件I/O对性能有很大的影响,如此频繁地执行文件I/O,对游戏的性能影响是很致命的。除了嵌套之外,如果需要用同一个CSB文件来创建多个对象,也会产生多次文件I/O。
在了解了CSLoader加载CSB资源的性能瓶颈之后,可以从多个方面来解决。
6.1.1 简单方案
首先可以使用一些简单的方法来缓解这个问题:不使用嵌套的CSB,改使用JSON格式可以大大提高嵌套CSB资源的加载效率。另外对于加载完返回的Node,使用一个池子进行管理,不用的时候回收到池子中缓存起来,而不是直接释放,下次再需要使用时先从池子里找,找不到再去加载。这些方法只能起到缓解的作用,并不能彻底解决CSLoader加载资源的性能瓶颈,效果还需要根据实际的应用场景来看。
6.1.2 缓存方案
该方案实现起来较简单,有更好的扩展性,并且可以彻底解决CSLoader加载资源的性能瓶颈,但会占用一些额外的内存,用来存储CSB文件的内容,不过CSB文件一般的体积都比较小,所以影响不大(严格来说,缓存方案占用的内存应该比克隆方案更少)。
如果使用的是CocoStudio 3.10以及以上的版本,可以使用CSLoader的新接口,传入Data对象来创建Node,这样需要加载多个相同的CSB文件时,可以先用FileUtils的getDataFromFile()方法将文件的内容读取到Data对象中,然后使用该Data对象来重复创建Node,也可以将Data对象管理起来,在任何时候都可以使用该对象来创建Node,或者释放该对象。对于3.10之前的版本,可以对CSLoader进行简单的修改,手动添加这个接口,改动并不大。使用这种方式需要自己手动编写一个CSB文件管理的类,然后使用其来管理CSB文件。
缓存方案的另一种实现方式则是修改FileUtils单例,我们的目标是通过修改FileUtils加载文件的接口,对CSB文件进行缓存,缓存的规则可以自己来制定,如小于1MB的CSB文件才进行缓存。除了CSB之外,任何我们会在短时间内重复加载的文件都可以进行缓存,可以根据我们的需求方便地进行调整,甚至文件的加密解密也可以放在这里实现。
由于FileUtils与平台相关,在不同的平台下有不同的子类实现,而且其子类的构造函数是私有的,我们无法通过继承重写的方法来重写其getDataFromFile()方法。而且FileUtils的单例指针是FileUtils内部的全局变量。这重重限制让我们无法做到在不修改引擎源码的情况下实现对FileUtils的扩展。所以只能修改FileUtils的源码。直接改动FileUtils的getDataFromFile接口是最简单的,首先需要为FileUtils定义一个成员变量来缓存Data对象。
std::map<std::string, Data> m_Cache;
然后重写getDataFromFile()方法,在getDataFromFile()方法中对CSB文件进行特殊处理,先判断是否有缓存,没有则调用getData()方法加载数据,缓存并返回。
Data FileUtils::getDataFromFile(const std::string& filename) { if (".csb" == FileUtils::getFileExtension(filename)) { if (m_Cache.find(filename) == m_Cache.end()) { m_Cache[filename] = getData(filename, false); } return m_Cache[filename]; } return getData(filename, false); }
也可以根据一个变量来设置是否开启缓存功能,以及提供清除缓存的接口,还可以在这个基础上对自己加密后的文件进行解密。
6.1.3 克隆方案
克隆方案也是一种可以彻底解决CSLoader加载资源性能瓶颈的方案,并且无须修改引擎的源码。克隆方案不仅可以解决CSLoader的性能瓶颈,在很多时候我们拥有了一个节点,希望将这个节点进行复制时,都可以使用克隆的方法。Cocos2d-x的Widget实现了clone()方法,但实现得并不是很好,很多东西没有被克隆,例如,在Widget下面添加一个Sprite节点,为Widget设置了分辨率适配规则,对于CocoStudio所携带的动画Action以及一些播放动画所需的扩展信息,这些都没有被克隆。下面这里提供一个克隆节点的方法,可以使用这个方法很好地克隆绝大部分的CSB节点,对于CSB中的粒子系统以及骨骼动画等节点并没有进行克隆(主要是因为没有用到),但根据下面的代码可以自己进行扩展,克隆它们。
#include "CsbTool.h" //Cocos2d-x在不同的版本下会包含一些不同的扩展信息,用于播放CSB动画,这些信息需要被 克隆 #if (COCOS2D_VERSION >= 0x00031000) #include "cocostudio/CCComExtensionData.h" #else #include "cocostudio/CCObjectExtensionData.h" #endif #include "cocostudio/CocoStudio.h" #include "ui/CocosGUI.h" USING_NS_CC; using namespace cocostudio; using namespace ui; using namespace timeline; //要克隆的节点类型,WidgetNode包含了所有的UI控件 enum NodeType { WidgetNode, CsbNode, SpriteNode }; //克隆扩展信息 void copyExtInfo(Node* src, Node* dst) { if (src == nullptr || dst == nullptr) { return; } #if (COCOS2D_VERSION >= 0x00031000) auto com = dynamic_cast<ComExtensionData*>( src->getComponent(ComExtensionData::COMPONENT_NAME)); if (com) { ComExtensionData* extensionData = ComExtensionData::create(); extensionData->setCustomProperty(com->getCustomProperty()); extensionData->setActionTag(com->getActionTag()); if (dst->getComponent(ComExtensionData::COMPONENT_NAME)) { dst->removeComponent(ComExtensionData::COMPONENT_NAME); } dst->addComponent(extensionData); } #else auto obj = src->getUserObject(); if (obj ! = nullptr) { ObjectExtensionData* objExtData = dynamic_cast<ObjectExtensionData*> (obj); if (objExtData ! = nullptr) { auto newObjExtData = ObjectExtensionData::create(); newObjExtData->setActionTag(objExtData->getActionTag()); newObjExtData->setCustomProperty(objExtData-> getCustomProperty()); dst->setUserObject(newObjExtData); } } #endif //复制Action int tag = src->getTag(); if (tag ! = Action::INVALID_TAG) { auto action = dynamic_cast<ActionTimeline*>(src-> getActionByTag (src->getTag())); if (action) { dst->runAction(action->clone()); } } } //克隆布局信息 void copyLayoutComponent(Node* src, Node* dst) { if (src == nullptr || dst == nullptr) { return; } //检查是否有布局组件 LayoutComponent * layout = dynamic_cast<LayoutComponent*>(src-> getComponent(__LAYOUT_COMPONENT_NAME)); if (layout ! = nullptr) { auto layoutComponent = ui::LayoutComponent:: bindLayoutComponent(dst); layoutComponent->setPositionPercentXEnabled(layout-> isPositionPercentXEnabled()); layoutComponent->setPositionPercentYEnabled(layout-> isPositionPercentYEnabled()); layoutComponent->setPositionPercentX(layout-> getPositionPercentX()); layoutComponent->setPositionPercentY(layout-> getPositionPercentY()); layoutComponent->setPercentWidthEnabled(layout-> isPercentWidthEnabled()); layoutComponent->setPercentHeightEnabled(layout-> isPercentHeightEnabled()); layoutComponent->setPercentWidth(layout->getPercentWidth()); layoutComponent->setPercentHeight(layout->getPercentHeight()); layoutComponent->setStretchWidthEnabled(layout-> isStretchWidthEnabled()); layoutComponent->setStretchHeightEnabled(layout-> isStretchHeightEnabled()); layoutComponent->setHorizontalEdge(layout->getHorizontalEdge()); layoutComponent->setVerticalEdge(layout->getVerticalEdge()); layoutComponent->setTopMargin(layout->getTopMargin()); layoutComponent->setBottomMargin(layout->getBottomMargin()); layoutComponent->setLeftMargin(layout->getLeftMargin()); layoutComponent->setRightMargin(layout->getRightMargin()); } } NodeType getNodeType(Node* node) { if (dynamic_cast<Widget*>(node) ! = nullptr) { return WidgetNode; } else if (dynamic_cast<Sprite*>(node) ! = nullptr) { return SpriteNode; } else { return CsbNode; } } Sprite* cloneSprite(Sprite* sp); //递归克隆子节点,如果是继承于Widget,可以调用clone()方法进行克隆,但在CocoStudio 中,Widget下可以包含其他非Widget节点,这些节点是不会被克隆的,所以需要递归检查一下 void cloneChildren(Node* src, Node* dst) { if (src == nullptr || dst == nullptr) { return; } for (auto& n : src->getChildren()) { NodeType ntype = getNodeType(n); Node* child = nullptr; switch (ntype) { case WidgetNode: //如果父节点也是Widget,则该节点已经被复制了 if (dynamic_cast<Widget*>(src) == nullptr) { child = dynamic_cast<Widget*>(n)->clone(); dst->addChild(child); } else { //如果节点已经存在,找到该节点 for (auto dchild : dst->getChildren()) { if (dchild->getTag() == n->getTag() && dchild->getName() == n->getName()) { child = dchild; break; } } } //对Widget的clone()方法没有克隆到的内容进行克隆 if (dynamic_cast<Text*>(n) ! = nullptr) { auto srcText = dynamic_cast<Text*>(n); auto dstText = dynamic_cast<Text*>(child); if (srcText && dstText) { dstText->setTextColor(srcText->getTextColor()); } } child->setCascadeColorEnabled(n->isCascadeColorEnabled()); child->setCascadeOpacityEnabled(n-> isCascadeOpacityEnabled()); copyLayoutComponent(n, child); cloneChildren(n, child); copyExtInfo(n, child); break; case CsbNode: child = CsbTool::cloneCsbNode(n); dst->addChild(child); break; case SpriteNode: child = cloneSprite(dynamic_cast<Sprite*>(n)); dst->addChild(child); break; default: break; } } } //克隆Sprite Sprite* cloneSprite(Sprite* sp) { Sprite* newSprite = Sprite::create(); newSprite->setName(sp->getName()); newSprite->setTag(sp->getTag()); newSprite->setPosition(sp->getPosition()); newSprite->setVisible(sp->isVisible()); newSprite->setAnchorPoint(sp->getAnchorPoint()); newSprite->setLocalZOrder(sp->getLocalZOrder()); newSprite->setRotationSkewX(sp->getRotationSkewX()); newSprite->setRotationSkewY(sp->getRotationSkewY()); newSprite->setTextureRect(sp->getTextureRect()); newSprite->setTexture(sp->getTexture()); newSprite->setSpriteFrame(sp->getSpriteFrame()); newSprite->setBlendFunc(sp->getBlendFunc()); newSprite->setScaleX(sp->getScaleX()); newSprite->setScaleY(sp->getScaleY()); newSprite->setFlippedX(sp->isFlippedX()); newSprite->setFlippedY(sp->isFlippedY()); newSprite->setContentSize(sp->getContentSize()); newSprite->setOpacity(sp->getOpacity()); newSprite->setColor(sp->getColor()); newSprite->setCascadeColorEnabled(true); newSprite->setCascadeOpacityEnabled(true); copyLayoutComponent(sp, newSprite); cloneChildren(sp, newSprite); copyExtInfo(sp, newSprite); return newSprite; } //克隆CSB节点 Node* CsbTool::cloneCsbNode(Node* node) { Node* newNode = Node::create(); newNode->setName(node->getName()); newNode->setTag(node->getTag()); newNode->setPosition(node->getPosition()); newNode->setScaleX(node->getScaleX()); newNode->setScaleY(node->getScaleY()); newNode->setAnchorPoint(node->getAnchorPoint()); newNode->setLocalZOrder(node->getLocalZOrder()); newNode->setVisible(node->isVisible()); newNode->setOpacity(node->getOpacity()); newNode->setColor(node->getColor()); newNode->setCascadeColorEnabled(true); newNode->setCascadeOpacityEnabled(true); newNode->setContentSize(node->getContentSize()); copyLayoutComponent(node, newNode); cloneChildren(node, newNode); copyExtInfo(node, newNode); return newNode; }