<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          想做更深入的加載優(yōu)化?剖析Cocos引擎底層架構(gòu)后,樂府大佬交出「90分答案」

          共 8383字,需瀏覽 17分鐘

           ·

          2022-05-21 20:28

          引言:無論是對引擎研發(fā)團(tuán)隊(duì)或是游戲開發(fā)團(tuán)隊(duì)來說,優(yōu)化的重要性都不言而喻。本次,來自樂府互娛「樂府小學(xué)生」在實(shí)際項(xiàng)目開發(fā)中,通過修改引擎源碼實(shí)現(xiàn)了更加深入的加載優(yōu)化。


          游戲江湖上曾流傳過一句名言:“三流的游戲做功能,二流的游戲做表現(xiàn),一流的游戲做優(yōu)化。”雖然有點(diǎn)扯,但并非全無道理,至少說明了優(yōu)化在做游戲中的重要性。本文將結(jié)合我參與的項(xiàng)目實(shí)例,分享我們是如何站在 Cocos Creator 的肩膀上”做更深入的加載優(yōu)化。


          本文所用引擎版本為 Cocos Creator 2.4.6。


          一、原音重現(xiàn)


          Cocos Creator 的加載流程



          以上是 loadRes 的加載流程,其中的關(guān)鍵步驟說明如下:

          • url tranform:主要是將工程路徑地址 /uuid 轉(zhuǎn)換成對應(yīng)的實(shí)際資源地址。

          • load res:主要是文件的 IO 過程,并把加載后的資源轉(zhuǎn)成對應(yīng)的 Json 對象或二進(jìn)制數(shù)組。

          • parse:主要是把加載到的資源解析成對應(yīng)的對象。

          • depends:獲取當(dāng)前資源的依賴,然后繼續(xù)調(diào)用開始的步驟加載。


          剖析 Prefab 的加載流



          以上流程左側(cè)清晰地展示了 Cocos Creator 的加載管線,從引擎源碼獲知從 url transform 至 depends 前的流程都可以插入自定義管線,具備較好的靈活性和擴(kuò)展性。


          右側(cè)部分為 cc.Spriteframe 資源的加載流程,這里為了展示區(qū)別,我們將其與 Cocos2d-x 中的 CCSprite 加載進(jìn)行對比:



          不難看出在 Cocos Creator 中創(chuàng)建一個 Sprite 會比 Cocos2d-x 時多兩個流程。而從 IO 次數(shù)上對比,單張貼圖的加載上 Cocos Creator 比 Cocos2d-x 多2次 IO(SpriteFrame 配置和 Texture2d 配置)。那么這兩個配置是否是必要的?


          答案還得從 Cocos Creator 本身的特性說起:


          1、SpriteFrame 配置文件(下文簡稱【配置1】):一個獨(dú)立的 json 文件,用來存儲一九宮,以及紋理大小偏移等信息??梢允辜y理自定義修改九宮圖等更靈活。對應(yīng)的就是下面屬性面板中的信息:



          TIPS :Cocos2d-x 時期的配置是保存在對應(yīng) ui 編輯器生成的配置文件里,其他沒有被界面引用的資源,需要在代碼中指定配置。


          2、Texture2d 的配置(下文簡稱【配置2】):主要定義紋理相關(guān)屬性。



          上圖顯示,有兩個屬性配置(WarpMode, FilterMode)會使我們使用圖片和修改配置上更靈活。


          綜上,Cocos Creator 加載流程多出的兩個配置是必要的。那么在效率上是否有優(yōu)化空間?


          二、選 A 還是選 C


          官方的構(gòu)建發(fā)布界面上有關(guān)于貼圖配置的合并選項(xiàng):



          官方文檔的解釋如下:


          內(nèi)聯(lián)所有 SpriteFrame

          自動合并資源時,將所有 SpriteFrame 與被依賴的資源合并到同一個包中。建議網(wǎng)頁平臺開啟,啟用后會略微增大總包體,多消耗一點(diǎn)點(diǎn)網(wǎng)絡(luò)流量,但是能顯著減少網(wǎng)絡(luò)請求數(shù)量。建議原生平臺關(guān)閉,因?yàn)闀龃鬅岣聲r的體積。

          合并圖集中的 SpriteFrame

          將圖集中的全部 SpriteFrame 合并到同一個包中。默認(rèn)關(guān)閉,啟用后能夠減少熱更新時需要下載的 SpriteFrame 文件數(shù)量,但如果圖集中的 SpriteFrame 數(shù)量很多,則可能會稍微延長原生平臺上的啟動時間。

          如果項(xiàng)目中圖集較多,有可能會導(dǎo)致 project.manifest 文件過大,建議勾選該項(xiàng)來減小 project.manifest 的體積。

          注意:在熱更新時,需要確保新舊項(xiàng)目中該功能的開啟/關(guān)閉狀態(tài)保持一致,否則會導(dǎo)致熱更新之后出現(xiàn)資源引用錯誤的情況。


          通俗的解釋就是:

          • 內(nèi)聯(lián):將 SpriteFrame 對應(yīng)的 json 文件【配置1】合并到了 prefab 中。

          • 合并圖集:把自動圖集中所有 SpriteFrame 合并到同一個文件中,類似 TexturePacker 的 plist 文件。


          各自的優(yōu)缺點(diǎn),在官方文檔中有詳細(xì)描述。那么有沒有一種解決方案,即能提高加載效率,又不影響啟動速度呢?


          三、90分答案


          本項(xiàng)目所采用的解決辦法是:

          1. 合并所有的 SpriteFrame 的配置,減少 IO。

          2. 將合并后的配置轉(zhuǎn)成二進(jìn)制文件,加快啟動速度。


          SpriteFrame 配置優(yōu)化


          下面是 SpriteFrame 配置信息,只有 "e8Ueib+qJEhL6mXAHdnwbi"(依賴)和中間的數(shù)據(jù)區(qū)是不同的:

          [
          ??1,
          ??[
          ????"e8Ueib+qJEhL6mXAHdnwbi"
          ??],
          ??[
          ????"_textureSetter"
          ??],
          ??[
          ????"cc.SpriteFrame"
          ??],
          ??0,
          ??[
          ????{
          ??????"name":?"default_btn_normal",
          ??????"rect":?[
          ????????0,
          ????????0,
          ????????40,
          ????????40
          ??????],
          ??????"offset":?[
          ????????0,
          ????????0
          ??????],
          ??????"originalSize":?[
          ????????40,
          ????????40
          ??????],
          ??????"capInsets":?[
          ????????12,
          ????????12,
          ????????12,
          ????????12
          ??????]
          ????}
          ??],
          ??[
          ????0
          ??],
          ??0,
          ??[
          ????0
          ??],
          ??[
          ????0
          ??],
          ??[
          ????0
          ??]
          ]


          • 解決方案


          1、相同的部分作為模板定義在代碼中(減少冗余數(shù)據(jù)),提取所有的差異部分合并到同一個文件中,組成如下配置:

          {[
          {
          ??????"name":?"default_btn_normal",
          ??????"rect":?[
          ????????0,
          ????????0,
          ????????40,
          ????????40
          ??????],
          ??????"offset":?[
          ????????0,
          ????????0
          ??????],
          ??????"originalSize":?[
          ????????40,
          ????????40
          ??????],
          ??????"capInsets":?[
          ????????12,
          ????????12,
          ????????12,
          ????????12
          ??????],
          ??????"depend":?"e8Ueib+qJEhL6mXAHdnwbi"?//?額外加入字段
          ?},
          ?...
          ?],
          ?[uuid1,uuid2,...]?//?額外加入字段為文件的uuid,與上面的順序保持一致
          }


          2、將文件轉(zhuǎn)成二進(jìn)制格式,這樣可以有效降低文件大小,提高初始化速度,并且減少數(shù)據(jù)和字段冗余。二進(jìn)制方案推薦使用 flatbuffers,具體使用方法可以參考網(wǎng)上教程或官方文檔。


          3、接管游戲下載流程,保證文件正常讀取。


          3.1 接管 IO:修改 builtin/jsb-adapter/engine/ jsb-fs-utils.js 文件,添加如下:

          setJsonReadHandler(handler)?{
          ????????fsUtils._customJsonLoadHandler?=?handler
          ????},

          ????readJson?(filePath,?onComplete)?{
          ????????let?jsonLoadhandler?=?fsUtils._customJsonLoadHandler
          ????????if?(jsonLoadhandler?&&?jsonLoadhandler(filePath,?onComplete))?{
          ????????????return
          ????????}
          ????????fsUtils.readFile(filePath,?'utf8',?function?(err,?text)?{
          ????????????var?out?=?null;
          ????????????if?(!err)?{
          ????????????????try?{
          ????????????????????out?=?JSON.parse(text);
          ????????????????}
          ????????????????catch?(e)?{
          ????????????????????cc.warn(`Read?json?failed:?path:?${filePath}?message:?${e.message}`);
          ????????????????????err?=?new?Error(e.message);
          ????????????????}
          ????????????}
          ????????????onComplete?&&?onComplete(err,?out);
          ????????});
          ????},

          注:這里是原生端的修改部分,網(wǎng)頁端可以通過自定義加載管線的方式處理


          3.2 數(shù)據(jù)還原:通過模板數(shù)據(jù)和二進(jìn)制數(shù)據(jù)對 SpriteFrame 格式做還原,是這里的數(shù)據(jù)區(qū)存為 flatbuffers 對象即可,用到的地方再去解析:

          [
          ??1,
          ??[
          ????"e8Ueib+qJEhL6mXAHdnwbi"
          ??],
          ??[
          ????"_textureSetter"
          ??],
          ??[
          ????"cc.SpriteFrame"
          ??],
          ??0,
          ??[
          ????//?flatbuffer對象
          ??],
          ??[
          ????0
          ??],
          ??0,
          ??[
          ????0
          ??],
          ??[
          ????0
          ??],
          ??[
          ????0
          ??]
          ]


          3.3 修改 CCSpriteframe.js 文件,修改解析:

          _deserialize:?function?(data,?handle)?{
          ????????if?(!CC_EDITOR?&&?data.bb)?{
          ????????????this._deserializeWithFlatbuffers(data);
          ????????????return;
          ????????}
          ????????...
          }


          Texture2d 配置優(yōu)化


          Texture2d 的配置如下:

          [
          ??1,
          ??0,
          ??0,
          ??[
          ????"cc.Texture2D"
          ??],
          ??0,
          ??[
          ????"0,9729,9729,33071,33071,0,0,1",
          ????-1
          ??],
          ??[
          ????0
          ??],
          ??0,
          ??[],
          ??[],
          ??[]
          ]


          與 SpriteFrame 配置相比,Texture2d 的配置簡單多了,里面的屬性值主要是與屬性面板和文件擴(kuò)展名有關(guān)。如果圖片的屬性都是默認(rèn)的,并且擴(kuò)展名是相同的情況下,Texture2d 配置是完全相同的,即項(xiàng)目中若有200張圖片資源,那200個圖片的配置文件就是完全相同的。


          • 解決方案


          通過 md5 比對所有的 Texture2d 配置文件,提取不同的文件,生成對應(yīng)的配置映射以便快速讀取。以我當(dāng)前的項(xiàng)目為例:有9000+圖片資源,最終比對下來也就只有5種類型,所以就直接把這5種配置在代碼中寫死,同樣在上面的接管流程中返回對應(yīng)的配置信息。


          優(yōu)化前后,iphone6 測試的加載速度提升了43%左右:



          Texture2d 加載流程優(yōu)化


          原生的紋理加載的流程,把紋理數(shù)據(jù)轉(zhuǎn)換成 ArrayBuffer 傳給 js,然后在 js 層再重新組裝返回 C++ 層,這里存在兩次數(shù)據(jù)傳遞的過程。流程如下:



          優(yōu)化的方向:在加載完成后,原生層一步到位。直接創(chuàng)建成 Texture2d 對象返回,減少中間的數(shù)據(jù)傳入過程。修改后的流程如下(紅框部分為省略的部分):



          注:修改為如上流程后,原生端的動態(tài)合圖將無法使用。但是大多數(shù)的原生開發(fā)都會使用壓縮紋理,并且壓縮紋理也是不支持動態(tài)合圖的。所以動態(tài)合圖的問題大家完全可以忽略。


          • 代碼修改如下:


          C++ 部分:

          cocos2d-x/cocos/scripting/js-bindings/manual/jsb_global.cpp

          ...
          if?(loadSucceed)
          {
          ??se::Object*?retObj?=?se::Object::createPlainObject();
          ??retObj->root();
          ??refs.push_back(retObj);
          ??cocos2d::renderer::Texture2D*?cobj?=?new?(std::nothrow)?cocos2d::renderer::Texture2D();
          ??auto?obj?=?se::Object::createObjectWithClass(__jsb_cocos2d_renderer_Texture2D_class);
          ??obj->setPrivateData(cobj);

          ??cocos2d::renderer::Texture::Options?options;
          ??options.bpp?=?imgInfo->bpp;
          ??options.width?=?imgInfo->width;
          ??options.height?=?imgInfo->height;

          ??options.glType?=?imgInfo->type;
          ??options.glFormat?=?imgInfo->glFormat;
          ??options.glInternalFormat?=?imgInfo->glInternalFormat;

          ??options.compressed?=?imgInfo->compressed;
          ??options.hasMipmap?=?false;
          ??options.premultiplyAlpha?=?imgInfo->hasPremultipliedAlpha;

          ??std::vector?images;
          ??cocos2d::renderer::Texture::Image?image;
          ??image.data?=?imgInfo->data;
          ??image.length?=?imgInfo->length;
          ??images.push_back(image);
          ??options.images?=?images;

          ??cobj->initWithOptions(options);

          ??retObj->setProperty("texture",?se::Value(obj));
          ??retObj->setProperty("width",?se::Value(imgInfo->width));
          ??retObj->setProperty("height",?se::Value(imgInfo->height));
          ??seArgs.push_back(se::Value(retObj));

          ??imgInfo?=?nullptr;
          }
          ...


          JS 代碼修改:

          builtin/jsb-adapter/builtin/jsb-adapter/HTMLImageElement.js

          set?src(src)?{
          ?this._src?=?src;
          ?jsb.loadImage(src,?(info)?=>?{

          ????if?(!info)?{

          ????????this._data?=?null;

          ????????return;

          ????}?else?if?(info?&&?info.errorMsg)?{

          ????????this._data?=?null;

          ????????var?event?=?new?Event('error');

          ????????this.dispatchEvent(event);

          ????????return;

          ????}

          ????this.width?=?this.naturalWidth?=?info.width;

          ????this.height?=?this.naturalHeight?=?info.height;

          ???if?(info.texture)?{

          ????????info.texture._ctor()

          ????????this.texture?=?info.texture

          ????}

          ????else?{

          ?????????...

          ????}

          ????this.complete?=?true;

          ????var?event?=?new?Event('load');

          ????this.dispatchEvent(event);

          });

          }

          engine/cocos2d/core/assets/CCTexture.js

          _nativeAsset:?{
          ????get?()?{
          ????????//?maybe?returned?to?pool?in?webgl
          ????????return?this._image;
          ????},
          ????set?(data)?{
          ????????if?(data.texture)?{
          ????????????this.initWithTexture(data.texture,?data.width,?data.height)
          ????????????return
          ????????}
          ????????...
          ????}
          },
          //?添加如下函數(shù)
          initWithTexture?(texture,?pixelsWidth,?pixelsHeight)?{
          ????this._texture?=?texture
          ????this.width?=?pixelsWidth;
          ????this.height?=?pixelsHeight;

          ????//?通知原生端更新配置,如果沒有修改texture屬性的,代碼基本跑不到。
          ????//?_updateNative標(biāo)志在當(dāng)前對象序列化的時候記錄如果配置中的信息和默認(rèn)值不一致時為true
          ????if?(this._updateNative)?{
          ????????var?opts?=?_getSharedOptions();
          ????????opts.minFilter?=?FilterIndex[this._minFilter];
          ????????opts.magFilter?=?FilterIndex[this._magFilter];
          ????????opts.wrapS?=?this._wrapS;
          ????????opts.wrapT?=?this._wrapT;
          ????????texture.update(opts,?true)?//?這里需要在原生端添加一個簡易的更新函數(shù)。就拿原來的更新函數(shù)提出紋理數(shù)據(jù)就好了,這里就不貼了。
          ????}
          ????this.loaded?=?true;
          ????this.emit("load");
          ????return?true;
          },


          優(yōu)化前后,iphone6 測試的加載速度提升了?12%-15%?左右:



          以上統(tǒng)計(jì)的是 Prefab 加載前后的數(shù)據(jù),包含了異步加載紋理的時間,所以會有時間較長的情況,但是同步耗時的地方基本沒了,并且在 iphone6 上已經(jīng)感受不到明顯的卡頓了。


          四、附加題


          spine 加載優(yōu)化


          由于 spine 的骨骼動畫是在原生端單獨(dú)加載的,所以在 js 加載的時候可以移除 spine 骨骼加載,減少一次 IO。


          • 修改文件如下:deserialize.js

          function?deserialize?(json,?options)?{
          ????...????
          ????//?不是原生端或者不是骨骼文件,spine原生端不加載骨骼文件
          ????asset._native?&&?(asset.__nativeDepend__?=?!CC_JSB?||?!(asset?instanceof?sp.SkeletonData));

          ????pool.put(tdInfo);
          ????return?asset;
          }


          路徑搜索(fullPathForFilename)


          由于第一次路徑填充的時候,需要從所有的路徑里去查找。從小米5上測試發(fā)現(xiàn)每次路徑檢查需要消耗 2ms 左右。正常我們會有兩個路徑:一個更新路徑,一個是當(dāng)前包路徑。所以小米5上一個文件檢索至少要 4ms+。


          • 解決方案:

          自己生成一個路徑映射表。因?yàn)榇虬透碌臅r候文件有哪些都是確定的。這樣就可以使文件查找的速度降到 50μs 以下。




          本文主要是想分享一個加載優(yōu)化的思路和方向給大家,感興趣的小伙伴可以點(diǎn)擊文末【閱讀原文】前往論壇專貼一起交流討論:

          https://forum.cocos.org/t/topic/134363


          往期精彩

          瀏覽 118
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  男女视频网址 | 影音先锋最新男人资源 | 亚洲第一视频网 | 97人人爽 | 国产精品激情综合 |