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;
        }