- 精通Cocos2d-x游戲開發(進階卷)
- 王永寶
- 2492字
- 2020-11-28 22:37:03
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; }