官术网_书友最值得收藏!

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;
        }
主站蜘蛛池模板: 永登县| 灵丘县| 航空| 德令哈市| 文水县| 阿鲁科尔沁旗| 阳西县| 和平区| 游戏| 裕民县| 巴南区| 嘉兴市| 镇宁| 内江市| 本溪市| 新营市| 娄底市| 龙游县| 达拉特旗| 保山市| 文成县| 镇坪县| 类乌齐县| 信阳市| 通州区| 砀山县| 衡东县| 泸定县| 达孜县| 外汇| 永济市| 连云港市| 渝中区| 隆林| 杨浦区| 获嘉县| 通化县| 宜宾市| 师宗县| 普定县| 平顶山市|